嵌入式C语言核心基础——指针与结构体深度精讲
嵌入式C语言核心基础——指针与结构体深度精讲作者黒漂技术佬 | 发布于CSDN搞嵌入式开发指针和结构体是绕不过去的两座大山。很多新手一看到int **p就头疼一碰到struct和指针混用就直接放弃治疗。其实这俩东西并没有那么恐怖关键是要理解其背后的内存模型。今天这篇文章咱们就从底层内存的角度把指针和结构体掰开揉碎讲清楚。一、指针到底是什么1.1 从内存地址说起计算机的内存可以想象成一排带编号的抽屉每个抽屉能放一个字节8 bit的数据。这个编号就是内存地址。指针本质上就是一个存储内存地址的变量。inta42;int*pa;// p 存储的是变量 a 的内存地址printf(a 的值: %d\n,a);// 42printf(a 的地址: %p\n,a);// 类似 0x7ffeefbff5acprintf(p 存储的地址: %p\n,p);// 与 a 相同printf(p 指向的值: %d\n,*p);// 42这里有几个核心符号需要记牢符号含义说明取地址符获取变量的内存地址*解引用符通过地址访问对应内存中的值*p声明时指针类型声明表示 p 是一个指向 int 的指针1.2 指针的大小指针的大小不取决于它指向的数据类型而取决于系统的地址总线宽度printf(int* 大小: %zu\n,sizeof(int*));// 32位系统为464位系统为8printf(char* 大小: %zu\n,sizeof(char*));// 同上在32位单片机如STM32F103上指针是4字节在64位Linux系统上指针是8字节。这点在跨平台开发时尤其要注意。二、指针运算指针之所以强大是因为它可以做运算。但指针运算不是简单的数学加减而是按指向类型的大小步进。2.1 指针加减法intarr[5]{10,20,30,40,50};int*parr;// p 指向 arr[0]printf(%d\n,*p);// 10printf(%d\n,*(p1));// 20p1 实际偏移了 sizeof(int)4 字节printf(%d\n,*(p3));// 40p 1并不是地址加1而是地址加sizeof(int)也就是跳到下一个int元素。这就是为什么数组名本质上是指向首元素的指针。2.2 指针减法与距离计算两个指向同一数组的指针可以做减法结果是它们之间的元素个数intarr[5]{10,20,30,40,50};int*p1arr[1];int*p2arr[4];printf(距离: %ld\n,p2-p1);// 3表示相隔3个int元素2.3 指针与数组遍历intarr[5]{10,20,30,40,50};// 用指针遍历数组for(int*parr;parr5;p){printf(%d ,*p);}// 输出: 10 20 30 40 50三、多级指针3.1 二级指针的概念指针本身也是变量也有自己的地址。存储指针地址的变量就是二级指针。inta100;int*pa;// 一级指针指向 aint**ppp;// 二级指针指向 pprintf(%d\n,**pp);// 100内存关系如下变量名 内存地址 存储的值 a 0x1000 100 p 0x2000 0x1000a的地址 pp 0x3000 0x2000p的地址3.2 二级指针的实际用途二级指针在嵌入式开发中最常见的场景是动态数组的数组指针以及函数内修改外部指针// 在函数内动态分配内存通过二级指针回传voidalloc_buffer(char**buf,intsize){*buf(char*)malloc(size);}intmain(){char*bufferNULL;alloc_buffer(buffer,128);// 传入 buffer 的地址if(buffer!NULL){strcpy(buffer,hello embedded);printf(%s\n,buffer);free(buffer);}return0;}四、结构体定义与使用4.1 为什么需要结构体嵌入式开发中经常需要把一组相关的数据打包在一起。比如一个传感器节点包含温度、湿度、电池电量等信息。结构体就是C语言中将不同类型数据组合在一起的工具。4.2 结构体的定义方式// 方式一先定义结构体类型再声明变量structSensor{floattemperature;// 温度floathumidity;// 湿度uint8_tbattery;// 电池电量百分比};structSensornode1;// 声明变量时必须带 struct 关键字// 方式二定义类型的同时声明变量structSensor{floattemperature;floathumidity;uint8_tbattery;}node2;// 方式三用 typedef 起别名推荐typedefstruct{floattemperature;floathumidity;uint8_tbattery;}Sensor_t;Sensor_t node3;// 直接用别名声明简洁4.3 结构体初始化与访问// 初始化Sensor_t node{.temperature25.5f,.humidity60.0f,.battery85};// 访问成员printf(温度: %.1f℃\n,node.temperature);printf(湿度: %.1f%%\n,node.humidity);printf(电量: %d%%\n,node.battery);使用.指定初始化器C99特性可以只初始化需要的成员未指定的自动置零这在嵌入式里非常实用。五、指针与结构体结合5.1 结构体指针当结构体作为参数传递或动态分配时我们通常使用结构体指针避免拷贝整个结构体。typedefstruct{floattemperature;floathumidity;uint8_tbattery;}Sensor_t;// 通过指针访问成员用 - 运算符voidprint_sensor(constSensor_t*s){printf(温度: %.1f℃\n,s-temperature);printf(湿度: %.1f%%\n,s-humidity);printf(电量: %d%%\n,s-battery);}intmain(){Sensor_t node{25.5f,60.0f,85};print_sensor(node);// 传地址避免结构体拷贝return0;}.和-的区别要记清楚运算符使用场景示例.通过结构体变量访问成员node.temperature-通过结构体指针访问成员s-temperature5.2 动态分配结构体在需要运行时创建结构体实例时配合malloc使用Sensor_t*create_sensor(floattemp,floathum,uint8_tbat){Sensor_t*s(Sensor_t*)malloc(sizeof(Sensor_t));if(sNULL){returnNULL;// 分配失败}s-temperaturetemp;s-humidityhum;s-batterybat;returns;}intmain(){Sensor_t*nodecreate_sensor(26.8f,55.0f,90);if(node!NULL){print_sensor(node);free(node);// 用完记得释放nodeNULL;// 避免悬空指针}return0;}5.3 结构体内存对齐结构体在内存中并不是简单地把成员紧挨着排列编译器会进行内存对齐以提高CPU访问效率。typedefstruct{chara;// 1字节intb;// 4字节charc;// 1字节}AlignDemo;printf(sizeof(AlignDemo) %zu\n,sizeof(AlignDemo));// 在默认对齐下结果通常是 12而不是 6内存布局如下假设4字节对齐偏移: 0 1 2 3 4 5 6 7 8 9 10 11 内容: [a] [pad pad pad] [ b (4字节) ] [c] [pad pad pad]如果需要节省内存在资源受限的嵌入式设备上很常见可以使用#pragma pack或__attribute__((packed))#pragmapack(push,1)// 设置1字节对齐typedefstruct{chara;intb;charc;}PackedDemo;#pragmapack(pop)printf(sizeof(PackedDemo) %zu\n,sizeof(PackedDemo));// 6六、总结指针和结构体是嵌入式C语言的基石。掌握指针的关键在于理解内存模型——指针就是地址指针运算按类型步进。结构体则是组织数据的利器配合指针使用可以高效传递复杂数据。结构体内存对齐在实际工程中必须关注它直接影响通信协议的解析和内存占用。把这些概念吃透后面的位运算、函数指针、回调机制等内容才能顺利展开。