嵌入式技术
前言
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量。
本质还是一个变量,指针提供了一种对存储位置的动态访问手段,(相对于普通变量而言,普通变量只能访问自己所占的存储位置)
内存地址对齐,是计算机在内存中的数据排列、访问数据的方式,包含了基本数据对齐和结构体数据对齐的两种相互独立又相互关联的部分。
现代计算机在内存中读写数据是按字节块进行操作,理论上任意类型的变量访问可以从任何地址开始,但是计算机系统对任意数据类型在内存中存放位置有限,它会要求这些数据的首地址的值为K(4位或者8位)的整数倍。
如何踩坑的?
在一份十分优秀的代码中,指针的使用率占比很高,因为指针能让代码实现变得更自由、更高效和更方便等诸多优点,可对于不十分熟悉指针的朋友来说,用起来也许就是灾难(常见的就是程序跑飞)
因此,通过指针的使用率大概就能判断一个人的编程能力水平
请看下面的代码,运行结果是怎么样的呢?
// 假设数组首地址为 0x00004000,符合内存对齐:4的倍数 static unsigned char sg_arrBuf[100]; int main() { memset(sg_arrBuf, 0, sizeof(sg_arrBuf)); // 地址为 0x00004000 uint8_t *pucVal = (uint8_t *)&sg_arrBuf[0]; // 地址为 0x00004001 uint16_t *puiVal = (uint16_t *)&sg_arrBuf[1]; *pucVal = 20; // HEX: 0x14 *puiVal = 2000; // HEX: 0x07d0 printf("HEX: "); for (int i = 0; i < 3; i++) { printf("%02x ", sg_arrBuf[i]); } printf(" "); return 0; }
很多朋友期望的结果如下(小端模式):
HEX: 14 d0 07
事实真的一定如此吗?
不一定!
也许部分朋友在自己电脑上打开 VS 复制粘贴运行了,编译后运行,结果还真和上面一样!!!你这不是在忽悠人吗!!!
那有试过在 MCU 上跑过吗?是不是程序跑飞了?
为什么?
当计算机读取或写入内存地址时,它将以字(word)大小的块进行存储。数据对齐意味着将数据放在等于字长的倍数的内存偏移处,正是由于这种CPU处理内存的方式从而提高了系统的性能。大多数CPU只能访问内存对齐的地址。
意味着部分系统架构体系对于未对齐的地址进行访问时会发生异常,如果尝试去访问内存未对齐的变量进行操作会导致总线错误。它只支持对齐访问。
比如我们访问一个 4 字节 (Double Word) 型的变量时,如果这个变量的起始地址是能被 4 整除的话,我们说这种访问是双字节对齐的。如果访问一个 2 字节 ( Word ) 变量,当起始地址能被 2 整除时是对齐的。访问字节 ( Byte ) 型变量,总是对齐的。
根据这个就能很快锁定问题原因了,那就是程序运行到这个位置就导致总线错误,从而跑飞了。
// 地址为 0x00004001,未对齐 *puiVal = 2000; // HEX: 0x07d0
预防及解决措施
关于上述的写法,有些朋友可能在电脑端实现了某个功能,电脑测试没有任何问题,但是一旦移植到 MCU 上就不行,那么抓紧检查一下是不是这个问题呢。
因此,为了确保我们的代码有很高的移植性和稳定性,那么一定要预防这种情况,可以通过采用访问字节 ( Byte ) 型变量,也可以使用memcpy的方式处理这种操作就能避免这种问题。
// 假设数组首地址为 0x00004000,符合内存对齐: 4的倍数 static unsigned char sg_arrBuf[100]; int main() { memset(sg_arrBuf, 0, sizeof(sg_arrBuf)); uint8_t ucVal = 20; uint16_t uiVal = 2000; memcpy(&sg_arrBuf[0], &ucVal, 1); memcpy(&sg_arrBuf[1], &uiVal, 2); printf("HEX: "); for (int i = 0; i < 3; i++) { printf("%02x ", sg_arrBuf[i]); } printf(" "); return 0; }编辑:黄飞
全部0条评论
快来发表一下你的评论吧 !