深入理解C指针

news2024/11/17 19:43:50

深入理解C指针

​#C语言 #​ #C指针 #​

1 认识指针

指针:一个存放内存地址的变量

1.1 指针和内存

image

阅读指针声明时候,可以选择倒过来读,会更容易理解。

指针被赋值为NULL时候,会被解释为二进制0.

void指针
具有和char指针相同的形式和内存对齐方式。
只能用作数据指针,不能用作函数指针。

全局指针和静态指针在程序启动时候被初始化为NULL。

1.2 指针的类型和长度

size_t类型是无符号整数,经常用于循环计数器、数组索引等。

在部分for循环中 如果中间的判断条件为 size_t a >= 0​,则可能会出错,该循环不会停止

例如 for (szie_t i = n; i >= 0; i--) {...}​, 当i为零时,由于是无符号整数,再减1还为整数,所以一直循环。(如果希望使用size_t 中间判断可以改为 ​i != SIZE_MAX​)

指针的长度可以通过sizeof​操作符判断

1.3 指针操作符

image

1.3.1 指针算数运算

  • 指针加上/减去整数
    给指针加上⼀个整数实际上加的数是这个整数和指针数据类型对应字节数的乘积
  • 两个指针相减
    通常是判断数组中的元素顺序
  • 比较指针
    判断数组元素的相对顺序

数组的名字,返回的只是数组地址,也就是数组第⼀个元素的地址。

1.4 指针的常见用法

1.4.1 多次间接引用

例如传统的argv 和argc 参数来给main 函数传递程序参数

俗称指针的指针

1.4.2 常量与指针

1、可以将指针定义为指向常量,这意味着不能通过指针修改它所引⽤的值。

const int *p​; 不可以修改p指向的值,但是可以更改p的指向,让它指向其他值

int const *p​和const int *p​ 是等价的

  • pci 可以被修改为指向不同的整数常量;

  • pci 可以被修改为指向不同的⾮整数常量;

  • 可以解引pci 以读取数据;

  • 不能解引pci 从⽽修改它指向的数据。

2、指向非常量的常量指针

意味着指针不可以变,但是指向的数据可以变(因此如果指向const定义的一些非指针变量会出错)

例如int *const cpi = ...

  • cpi 必须被初始化为指向 常量变量;

  • cpi 不能被修改;

  • cpi 指向的数据可以被修改。

3、指向常量常量指针

不可以修改指针、不可以修改指针指向的数据

4、指向”指向常量常量指针“的指针

下面第二行为指向”指向常量常量指针“的指针

const int * const cpci = &limit;
const int * const * pcpci;

2 C的动态内存管理

2.2 动态内存分配函数

stdlib.h​image

malloc

void* malloc(size_t);

如果内存不足,返回NULL,此外新分配的内存会包含垃圾数据。

calloc
分配内存同时清空内存,也即设置为二进制0

void *calloc(size_t numElements, size_t elementSize);

realloc

image

alloca

函数返回后会自动释放内存,但是要求系统运行是基于栈

c99中引入了可变长数组

char* buf[size];

2.3 free

void free(void *ptr);

如果传入空指针,则什么都不做

2.5 动态内存分配技术

资源获取即初始化

GNU的扩展要⽤到RAII_VARIABLE 宏,它声明⼀个变量,然后给变量关联如

下属性:
⼀个类型;
创建变量时执⾏的函数;
变量超出作⽤域时执⾏的函数

#define RAII_VARIABLE(vartype,varname,initval,dtor) \

void *dtor* ## varname (vartype * v) { dtor(*v); } \

vartype varname _*attribute__((cleanup(_dtor* ## varname))) =

(initval)


void raiiExample() {
    RAII_VARIABLE(char*, name, (char*)malloc(32), free);
    strcpy(name,"RAII Example");
    printf("%s\n",name);
}

3 指针和函数

3.1 程序的栈和堆

程序栈是支持函数执行的内存区域,通常和堆共享。
程序栈在这个区域的下部,堆是上部。

程序栈存放栈帧 (stack frame) ,栈帧存放函数参数和局部变量。

栈帧: 组成

  • 返回地址:函数完成后要返回的程序内部地址。

  • 局部数据存储:为局部变量分配的内存。

  • 参数存储:为函数参数分配的内存。

  • 栈指针和基指针:运⾏时系统⽤来管理栈的指针。

栈指针通常指向栈顶部。基指针(帧指针)通常存在并指向栈帧内部的地

址,⽐如返回地址,⽤来协助访问栈帧内部的元素。这两个指针都不是C指

针,它们是运⾏时系统管理程序栈的地址。

样例

float average(int *arr, int size) {
    int sum;
    printf("arr: %p\n",&arr);
    printf("size: %p\n",&size);
    printf("sum: %p\n",&sum);
    for(int i=0; i<size; i++) {
        sum += arr[i];
    }
    return (sum * 1.0f) / size;
}

arr: 0x500
size: 0x504
sum: 0x480

参数地址和局部变量地址之间的空档,保存的是运⾏时系统管理栈所需要的
其他栈帧元素。

系统在创建栈帧时,将参数以跟声明时相反的顺序推到帧上,最后推⼊局部变量,如图所⽰。在这个例⼦中,arr 在size之后被推⼊。通常,接下来会推⼊函数调⽤的返回地址,然后是局部变量。推⼊它们的顺序跟其在代码中列出的顺序相反。

image

3.2 通过指针传递和返回数据

传递指向常量的指针是C中常⽤的技术,效率很⾼,因为我们只传了数据的地址,能避免某些情况下复制⼤量内存。不过,如果只是传递指针,数据就能被修改。如果不希望数据被修改,就要传递指向常量的指针。

实现自己的free函数

#define safeFree(p) saferFree((void**)&(p))

void saferFree(void **pp) {
    if (pp != NULL && *pp != NULL) {
        free(*pp);
        *pp = NULL;
    }
}

用法:

int main() {
    int *pi;
    pi = (int*) malloc(sizeof(int));
    *pi = 5;
    printf("Before: %p\n",pi);
    safeFree(pi);
    printf("After: %p\n",pi);
    safeFree(pi);
    return (EXIT_SUCCESS);
}

第⼆次调⽤safeFree 宏给它传递NULL 值不会导致程序终⽌,因为saferFree 函数检测到这种情况并忽略了这个操作。

3.3 函数指针

顾虑:处理器可能无法配置流水线作分支预测

3.3.1 声明函数指针

image

3.3.2 使用函数指针

#include <stdio.h>
#include <malloc.h>

int (*fptr1)(int);
int square(int num) {
    return num * num;
}

int main() {
    int n = 5;
    fptr1 = square; // fptr1 = &square
    printf("Hello, World! %d\n", fptr1(n));
    return 0;
}

输出

Hello, World! 25

image

有时候可以为函数指针声明一个类型定义
typedef int (*funcptr)(int);

#include <stdio.h>
#include <malloc.h>

typedef int (*funcptr)(int);
int square(int num) {
    return num * num;
}

int main() {
    int n = 5;
    funcptr fptr1;
    fptr1 = &square;
    printf("Hello, World! %d\n", fptr1(n));
    return 0;
}

3.3.3 传递函数指针

简单例子

int add(int num1, int num2) {
    return num1 + num2;
}

int sub(int num1, int num2) {
    return num1 - num2;
}

typedef int (*fptrOperator)(int, int);

int compute(fptrOperator operator, int num1, int num2) {
    return operator(num1, num2);
}

void test_compute() {
    printf("%d\n",compute(add, 5, 6));
    printf("%d\n",compute(sub, 5, 6));

}

输出
11
-1

3.3.4 返回函数指针

使用一个函数,基于输入的字符返回相应的函数指针。

fptrOperation select(char opcode) {
    switch(opcode) {
        case '+': return add;
        case '-': return subtract;
    }
}

int evaluate(char opcode, int num1, int num2) {
    fptrOperation operation = select(opcode);
    return operation(num1, num2);
}

printf("%d\n",evaluate('+', 5, 6));
printf("%d\n",evaluate('-', 5, 6));

3.3.5 使用函数指针数组

函数指针数组可以基于某些条件选择要执⾏的函数

typedef int (*operation)(int, int);
operation operations[128] = {NULL};
// 或
int (*operations[128])(int, int) = {NULL};

// 数组赋值
void initializeOperationsArray() {
    operations['+'] = add;
    operations['-'] = subtract;
}

int evaluateArray(char opcode, int num1, int num2) {
    fptrOperation operation;
    operation = operations[opcode]; // 数组函数指针选择
    return operation(num1, num2);
}

3.3.6 比较函数指针

add 函数被赋给fptr1 函数指针,然后和add 函数的地址做⽐较

fptrOperation fptr1 = add;
if(fptr1 == add) {
    printf("fptr1 points to add function\n");
} else {
    printf("fptr1 does not point to add function\n");
}

主要是可以方便部分情况下动态修改操作

3.3.7 转换函数指针

可以将指向某个函数的指针转换为其他类型的指针

4 指针和数组

4.1 数组概述

4.1.1 一维数组

一维数组是线性结构,用索引访问成员,由于c语言没有强制规定边界,无效索引会造成不可预期的行为。
int vector[5]

数组的内部表⽰不包含其元素数量的信息,数组名字只是引⽤了⼀块内存。对数组做sizeof 操作会得到为该数组分配的字节数,要知道元素的数量,只需将数组长度除以元素长度,如下所⽰,打印结果是5:​printf("%d\n", sizeof(vector)/sizeof(int));

4.1.2 二维数组

需要程序把二位数组映射到一维,也即先把数组的第一行放进内存,接着是第二行。。

4.2 指针表示法和数组

pv[i] 等价于 *(pv + i)

⽅括号表⽰法会取出pv 中包含的地址,⽤指针算术运算把索引i 加上,然后解引新地址返回其内容。

数组和指针的差别

int vector[5] = {1, 2, 3, 4, 5};
int *pv = vector;

vector[i] ⽣成的代码和*(vector+i) ⽣成的不⼀样,vector[i] 表⽰法⽣成的机器码从位置vector 开始,移动i 个位置,取出内容。⽽*(vector+i) 表⽰法⽣成的机器码则是从vector 开始,在地址上增加i ,然后取出这个地址中的内容。尽管结果是⼀样的,⽣成的机器码却不⼀样

sizeof 操作符对数组和同⼀个数组的指针操作也是不同的。对vector 调⽤sizeof 操作符会返回20,就是这个数组分配的字节数。

4.3 malloc创建一维数组

int *pv = (int*) malloc(5 * sizeof(int));
for(int i=0; i<5; i++) {
    pv[i] = i+1;
}
//或者
for(int i=0; i<5; i++) {
    *(pv+i) = i+1;
}

警告 在上个例⼦中我们⽤的是 (pv+i) ⽽不是pv+i ,因为解引操作符的优先级⽐加操作符⾼,先解引第⼆个表达式的指针,得到指针所引⽤的值,然后再给这个整数加上i 。这不是我们要的效果,⽽且,如果我们把这个表达式作为左值,编译器会抱怨。所以,为了让代码正确⼯作,我们需要强制先做加法,然后才是解引操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1363341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker 安装elasticsearch、kibana、cerebro

安装步骤 第一步安装 docker 第二步 拉取elasticsearch、kibana、cerebro 镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.10.2 docker pull docker.elastic.co/kibana/kibana:7.10.2 docker pull lmenezes/cerebro:latest第三步、创建 容器 创建e…

SQL Server从0到1——写shell

xp_cmdshell 查看能否使用xpcmd_shell&#xff1b; select count(*) from master.dbo.sysobjects where xtype x and name xp_cmdshell 直接使用xpcmd_shell执行命令&#xff1a; EXEC master.dbo.xp_cmdshell whoami 发现居然无法使用 查看是否存在xp_cmdshell: EXEC…

深入理解计算机系统(1):开始

计算机系统是由硬件和系统软件组成的&#xff0c;它们共同工作来运行应用程序。虽然系统的具体实现方式随着时间不断变化&#xff0c;但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和软件组件&#xff0c;它们又执行着相似的功能。 计算机系统 信息就是位上下…

❀记忆冒泡、选择和插入排序算法思想在bash里运用❀

目录 冒泡排序算法:) 选择排序算法:) 插入排序算法:) 冒泡排序算法:) 思想&#xff1a;依次比较相邻两个元素&#xff0c;重复的进行直到没有相邻元素需要交换&#xff0c;排序完成。 #!/bin/bash arr(12 324 543 213 65 64 1 3 45) #定义一个数组 n${#arr[*]} #获取数组…

非工程师指南: 训练 LLaMA 2 聊天机器人

引言 本教程将向你展示在不编写一行代码的情况下&#xff0c;如何构建自己的开源 ChatGPT&#xff0c;这样人人都能构建自己的聊天模型。我们将以 LLaMA 2 基础模型为例&#xff0c;在开源指令数据集上针对聊天场景对其进行微调&#xff0c;并将微调后的模型部署到一个可分享的…

【STM32】STM32学习笔记-定时器定时中断 定时器外部时钟(14)

00. 目录 文章目录 00. 目录01. 定时器中断相关API1.1 TIM_InternalClockConfig1.2 TIM_TimeBaseInit1.3 TIM_TimeBaseInitTypeDef1.4 TIM_ClearFlag1.5 TIM_ITConfig1.6 TIM_Cmd1.7 中断服务函数1.8 TIM_ETRClockMode2Config 02. 定时器定时中断接线图03. 定时器定时中断示例0…

每日算法打卡:数的范围 day 7

文章目录 原题链接题目描述输入格式输出格式数据范围输入样例&#xff1a;输出样例&#xff1a; 题目分析示例代码 原题链接 789. 数的范围 题目难度&#xff1a;简单 题目描述 给定一个按照升序排列的长度为 n 的整数数组&#xff0c;以及 q 个查询。 对于每个查询&#…

数字孪生在虚拟现实(VR)中的应用

数字孪生在虚拟现实&#xff08;VR&#xff09;中的应用为用户提供了更深入、沉浸式的体验&#xff0c;同时通过数字孪生技术模拟真实世界的物理实体。以下是数字孪生在VR中的一些应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发…

12月笔记

#pragma once 防止多次引用头文件&#xff0c;保证同一个&#xff08;物理意义上&#xff09;文件被多次包含&#xff0c;内容相同的两个文件同样会被包含。 头文件.h与无.h的文件&#xff1a; iostream是C的头文件&#xff0c;iostream.h是C的头文件&#xff0c;即标准的C头文…

电话号码信息收集工具:PhoneInfoga | 开源日报 No.137

sundowndev/phoneinfoga Stars: 11.2k License: GPL-3.0 PhoneInfoga 是一个用于扫描国际电话号码的信息收集框架&#xff0c;它允许用户首先收集基本信息 (如国家、地区、运营商和线路类型)&#xff0c;然后使用各种技术来尝试找到 VoIP 提供商或识别所有者。该工具与一系列必…

[MySQL]视图索引以及连接查询案列

目录 1.视图 1.1视图是什么 1.2视图的作用 1.3操作 1.3.1创建视图 1.3.2视图的修改 1.3.3删除视图 1.3.4查看视图 2.索引 2.1什么是索引 2.2为什么要使用索引 2.3索引的优缺点 2.3.1优点 2.3.2缺点 2.4索引的分类 3.连接查询案列 4.思维导图 1.视图 1.1视图是什么 视图…

代码随想录-刷题第四十九天

121. 买卖股票的最佳时机 题目链接&#xff1a;121. 买卖股票的最佳时机 思路&#xff1a;动态规划五步曲 dp[i][0] 表示第i天持有股票所得最多现金&#xff0c;dp[i][1] 表示第i天不持有股票所得最多现金。 一开始现金是0&#xff0c;那么加入第i天买入股票&#xff0c;现金…

深入了解 RDD

深入了解 RDD 案例 明确需求&#xff1a; 在访问日志中&#xff0c;统计独立IP数量 TOP10 查看数据结构&#xff1a; IP&#xff0c;时间戳&#xff0c;Http&#xff0c;Method&#xff0c;Url…… 明确编码步骤 取出IP&#xff0c;生成一个只有IP的数据集简单清洗统计IP出现…

Tomcat Notes: Deployment File

This is a personal study notes of Apache Tomcat. Below are main reference material. - YouTube Apache Tomcat Full Tutorial&#xff0c;owed by Alpha Brains Courses. https://www.youtube.com/watch?vrElJIPRw5iM&t801s 1、Tomcat deployment1.1、Two modes of …

数据结构与算法教程,数据结构C语言版教程!(第二部分、线性表详解:数据结构线性表10分钟入门)九

第二部分、线性表详解&#xff1a;数据结构线性表10分钟入门 线性表&#xff0c;数据结构中最简单的一种存储结构&#xff0c;专门用于存储逻辑关系为"一对一"的数据。 线性表&#xff0c;基于数据在实际物理空间中的存储状态&#xff0c;又可细分为顺序表&#xff…

Java 基础知识点1 (含面试题)

本次Java 知识点主要是关于SE的相关基础&#xff0c;同时也包含了数据结构中的一些API&#xff0c;例如Set,List,Map等&#xff0c;最后也附上了相关重要的面试题&#xff0c;可供大家学习与参考&#xff01; 目录 重要知识点数据结构API面试题 重要知识点 Java 是一门面向对象…

linux下超级程序!在linux界面实现类图像化界面的操作体验!

linux下超级程序&#xff01;在linux界面实现类图像化界面的操作体验&#xff01; 本期带来一个超级程序&#xff01;在linux界面实现类图像化界面的操作体验。具体功能代码如下: 1500行完整代码想要完成部署&#xff0c;只需在本地创建一个LinuxGJ.sh的文件&#xff0c;然后…

传感数据分析——高通滤波与低通滤波

传感数据分析——高通滤波与低通滤波 文章目录 传感数据分析——高通滤波与低通滤波前言一、运行环境二、Python实现总结 前言 对于传感信号而言&#xff0c;我们可以提取其中的高频信息和低频信息&#xff0c;低频信息往往是信号的趋势&#xff0c;高频信息往往是一些突变或异…

构建自己的私人GPT

创作不易&#xff0c;请大家多鼓励支持。 在现实生活中&#xff0c;很多人的资料是不愿意公布在互联网上的&#xff0c;但是我们又要使用人工智能的能力帮我们处理文件、做决策、执行命令那怎么办呢&#xff1f;于是我们构建自己或公司的私人GPT变得非常重要。 一、本地部署…

win10下vscode+cmake编译C代码操作详解

0 工具准备 1.Visual Studio Code 1.85.1 2.cmake 3.24.01 前言 当我们只有一个.c文件时直接使用vscodeCode Runner插件即可完成编译&#xff0c;如果我们的工程很复杂包含多个.c文件时建议使用cmake来生成对应的make&#xff0c;指导编译器完成编译&#xff0c;否则会提示各…