加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
    • 布置场景
    • 问题是怎么发生的?
    • 原因是什么呢?
    • 如何处理呢?
  • 推荐器件
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

C语言为什么使用指针会导致死机?

06/26 08:50
1926
阅读需 10 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

STM32系列的M0内核刚出来的时候,为了降本,我有很多项目都需要移植到M0平台,本以为只是把底层驱动看一下就可以,却不想总是遇到莫名其妙的死机,着实折腾过一段时间的。今天在DIY遥控器的时候,突然又发现了一个当时遇到的问题,记录下来给大家提个醒,万一遇到的死机的情况,不妨思考一下这个问题。

布置场景

因为我的遥控器是基于无线RF通信的,因此我需要定义了两个帧结构,分别用数组来存放,然后再发送和接收函数中直接处理数组。帧结构的前四个字节作为通信的地址,也就是一个过滤ID,不符合这个ID的帧直接放弃掉。为了让系统中兼容更多的设备对,我把这个通信地址定义为4个字节,也就是一个uint32_t类型。数组及其索引值定义如下:

// 接收缓冲区长度定义#define DW_RX_MSG_LEN           18// 接收包中的地址索引#define    RX_ADDRESS_LL           0#define    RX_ADDRESS_LH           1#define    RX_ADDRESS_HL           2#define    RX_ADDRESS_HH           3...
uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收缓存定义

接收缓冲器如上定义完成后,我将在接收函数中处理这个接收到的帧,第一件事情肯定是比对地址是否匹配,假设我宏定义的地址为:

#define  ADDRESS  0x12345678

最简单的写法肯定是这样判断:

if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)    ){
}

上面这种写法很明了,我们比对每一个字节是否相等,如果都相等,我们认为这个帧是发送给我们的,但是这样的写法比较啰嗦,字数比较多,并且并不直观呀,好好的一个32bit的数据,非要拆分成4次比较,干起来效率一点都不高。

问题是怎么发生的?

C语言中一定要用指针才显得高级,这里很明显可以使用指针来取出数组的前四个字节,然后和我们的宏定义地址相比较,这样是非常简单高效的,大牛们常常都是这么写的。

if(*(uint32_t *)g_dw_rx_msg == ADDRESS){}

你看多简单,数组的名称就是数组的地址,也是第一个元素的地址,我们取出来转换成32bit整型,然后取出来直接和我们的地址比较。既能清楚的表达这是一个地址的比对,又能省不少敲字符的力气。这样也许在CortexM3上面大概率不会出现问题,但是在M0内核上大概率会出问题,会死机的。为什么总说是概率呢?我先来描述一下你可能遇到的现象。我按照指针的写法编译通过了,运行也没有问题,性能杠杠的,贼稳定。后来,突然增加了一个需求,于是我又定义了一些变量和数组,再编译,运行….

我擦,死机了!我就一直在新增的功能那里不断地查找问题,怎么也找不到那里写的有问题,只要我新定一个变量,并且在程序中使用它(防止编译器优化掉),就会死机,去掉就没事了。调试过程中,你一定遇到过这样的场景。当我们Debug一步一步的跟踪时,就会发现,程序死机之前执行了我们的判断语句。

原因是什么呢?

我们都知道,CortexM0内核是一个32位总线的,它的硬件无法处理32位未对齐的内存访问,那么他对于内存的访问地址一定是4的整数倍,否则就会出现无法访问内存的问题。这里,我们的判断语句中使用了指针的强制类型转换(uint3_t*),那么如果我们数组中的元素地址恰好不是4的整数倍的时候,在转化成uint3_t进行访问时,处理器就只能报错了。

为什么出错是概率性的呢?编译器会根据我们定义的全局变量和静态变量来安排变量的地址,并且数组这东西,如果定义成uint8_t类型的,他中间的某个元素地址对齐的可能性只有四分之一。也就是说,我们第一次编译成功,运行可靠的时候,恰好我们定义的全局变量合适,编译器把我们的数组起始地址正好安排在32位对齐的地址上。当我们再定义一个变量的时候,一定是定义了非32位对齐的,比如uint8_t,或者一个数组或者结构体,总之,它不是4字节的整数倍。这样,我们再编译,编译器就会重新给我们安排所有变量的地址,我们的数组的地址就恰好给挤到了一个地址不对齐的地方。我们再用32bit的指针类型去访问时,就会死机了。

如何处理呢?

有三种方法处理。第一种,要承认傻人有傻福,笨办法有笨办法的优点,有些事情投机取巧可能会赚,但大概率还是赔的。就好比我们的大A,你以为是投资,后来认为是投机,再后来,你以为是诈骗,现在你可能认为是抢劫了,其实,我们的大A本质上是捐款!跑题了,所以,我们老老实实按字节比较就可以了。

if((g_dw_rx_msg[RX_ADDRESS_LL] == (ADDRESS >> 0) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_LH] == (ADDRESS >> 8) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HL] == (ADDRESS >> 16) & 0xFF) &&   (g_dw_rx_msg[RX_ADDRESS_HH] == (ADDRESS >> 24) & 0xFF)    ){
}

第二种,如果你实在忍受不了这个样式,想让程序更直观的体现出来,它是一个地址的比对,那么就自己写一个函数,将四个字节取出来组成一个32bit的整型数。或者,你可以直接利用memcpy函数来实现。

uint32_t my_address = 0;memcpy(&my_address, &((uint32_t*)g_dw_rx_msg), sizeof(uint32_t));

第三种,如果你还是纠结于优雅和效率,那我们就不撞南墙不回头,编译器自身提供了一些指令,来强制我们定义的变量地址对齐。比如:__attribute__((aligned(4)))  如下定义:

__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收缓存

按照上面的定义方法,我们的数组其实地址就会被安排在32位地址对齐的位置上了,指针就可以大显身手了,不过要注意,如果我们的地址索引在数组内部不对齐也是不行的。比如,我们的地址是从数组的第1个地址开始的。(前面有一个第0个)

// 接收缓冲区长度定义#define DW_RX_MSG_LEN           18// 接收包中的地址索引#define    RX_ADDRESS_LL           1     //这里地址从数组的第1个元素开始#define    RX_ADDRESS_LH           2#define    RX_ADDRESS_HL           3#define    RX_ADDRESS_HH           4...
__attribute__((aligned(4))) uint8_t g_dw_rx_msg[DW_RX_MSG_LEN] = {0};  //接收缓存
if(*(uint32_t *)g_dw_rx_msg == ADDRESS){    // 我还得死啊!!!!}

推荐器件

更多器件
器件型号 数量 器件厂商 器件描述 数据手册 ECAD模型 风险等级 参考价格 更多信息
SN74HC14D 1 Texas Instruments 6-ch, 2-V to 6-V inverters with Schmitt-Trigger inputs 14-SOIC -40 to 85

ECAD模型

下载ECAD模型
$0.2 查看
TJA1051T/CM,118 1 NXP Semiconductors TJA1051 - High-speed CAN transceiver SOIC 8-Pin

ECAD模型

下载ECAD模型
$1.02 查看
HMC534LP5E 1 Hittite Microwave Corp Voltage Controlled Oscillator, 10600MHz Min, 11800MHz Max, ROHS COMPLIANT, PLASTIC, LEADLESS, QFN-32
$44.16 查看

相关推荐

电子产业图谱

多年硬件从业经验,专注分享从研发到供应链,再到精益制造过程中的经验和感悟!