在讨论这个问题之前,我们先要记住一个结论:
一般情况下,出于效率和兼容性的考虑,编译器会避免产生非对齐的操作。当且仅当编译器不知道(被蒙蔽)的情况下,才有可能产生隐性的非对齐的操作。
不知所云?让我们举一个简单的例子:
// 假设我们有一个函数,它要执行一个 32bit 的整数操作 extern void word_access ( uint32_t *pwTarget ); // 如果你这么用,显然是没有任何问题的 extern uint32_t wDemo; ... word_access (&wDemo); // 如果你这么做呢…… extern uint8_t chBuffer[16]; ... word_access ((uint32_t *)&chBuffer[1]);
不管你是否已经明白问题所在了,我们来简单分析下这段代码:
可能有人会问:既然代码已经写的清清楚楚——“我们使用的是一个非对齐的地址”——为什么编译器仍然会假装不知道呢?其实编译器并非不知道,如果我们直接这么写:
word_access (&chBuffer[1]);
编译器立马就会报告Error:“指针的类型不符”。为了头疼医头,脚疼医脚的“屏蔽”这个Error,很多人会加入强制类型转换 (uint32_t *) 。实际上,从ANSI-C的标准来看,这个代码并没有任何问题,语法和逻辑上都讲得通。但是对齐是一个“潜规则”,你不遵守它,就会吃亏。这里,强制类型转换相当于直接给编译器蒙住了眼睛:“甭管之前看到了什么,反正现在这个指针,我说是对齐的就是对齐的!!!”
也许你不会直接写出这么傻的代码,但是下面的“高级”用法确更加稀松平常:
// 这是一个消息地图中常见的消息处理函数 void xxx_msg_handler( uint8_t *pchStream, uint16_t hwSize ) { // offset 0x00: 1 BYTE Command / Message uint8_t chCMD = pchStream[0]; // offset 0x01: 4 BYTE Serial Number of the frame uint32_t wSN = *(uint32_t *)&pchStream[1]; ... }
对于通信数据帧解析来说,上述用法在常见不过了,怎么样踩地雷了吧?这只是举一个例子,只要用到指针强制类型转换的地方,都是在“蒙蔽”编译器,都有可能受到对齐潜规则的惩罚。
因为 ARMv7-M 支持非对齐操作,具体请看 对齐(1)的内容,所以你幸免于难。但是,对如下的情况,你就绝无可能幸免:
非对齐操作的危害主要有以下几点:
注意:这里“易失性”意思就是,每次操作的时候:
是不是越听腿越哆嗦?啥?不哆嗦?莫装13,反正以后坑的是自己。珍爱生命,远离非对齐操作。
1、对第一个例子来说,要么避免给函数提供非对齐的地址,要么直接告诉编译器对应的函数处理的地址可能是非对齐的,直接修改函数原形即可:
// 假设我们有一个函数,它要执行一个 可能非对齐的 32bit 的整数操作 extern void word_access ( uint32_t __packed *pwTarget );
2、对第二个例子来说,由于数据帧的格式已经确定,因此,我们需要直接告诉编译器对目标数据的访问是非对齐的,对应的代码如下:
// 这是一个消息地图中常见的消息处理函数 void xxx_msg_handler( uint8_t *pchStream, uint16_t hwSize ) { // offset 0x00: 1 BYTE Command / Message uint8_t chCMD = pchStream[0]; // offset 0x01: 4 BYTE Serial Number of the frame uint32_t wSN = *(uint32_t __packed*)&pchStream[1]; ... }
_______________正文结束________________
如果你喜欢我的思维,欢迎订阅 裸机思维