给出以下代码:
unsigned char *packet_data = (unsigned char *)malloc(7);
memset(packet_data, 0, 7);
uint16_t crc = 0xa8a9;
*((uint16_t *)&packet_data[5]) = (crc >> 8) | (crc << 8);
for (int i = 0; i < 7; i++) {
printf("%02X ", packet_data[i]);
}
printf("\n");
在我的Mac(x86_64)上,输出正如预期的那样 00 00 00 00 00 A8 A9 (A8进入字节5(从0开始计数))。用clang编译(LLVM 7.3.0)
在armv5tejl机器上,输出 00 00 00 00 A8 A9 00 (A8进入字节4(从0开始计数))。在这种情况下,如果我们在源代码上将5
切换为4
,则会产生完全相同的输出。使用gcc 4.6.3编译,like this on the Godbolt compiler explorer。
这两台机器都是小端的。
为什么会这样?
答案 0 :(得分:5)
假设类型uint16_t的对齐要求为2,那么您的代码具有未定义的行为 1 ,因为此指针:(uint16_t *)&packet_data[5]
未正确对齐。
如果使用对齐的偏移量(如4),则结果应与定义的行为相同。
1 (引自:ISO / IEC 9899:201x 6.3.2.3指针7):
指向对象类型的指针可以转换为指向不同对象类型的指针。如果
结果指针未正确对齐引用类型,行为是
未定义。
答案 1 :(得分:4)
您的首要问题 - 原因 - 是因为这就是硬件的工作原理。
您已经引用了C标准,说该行为未定义,因此任何结果都是C中的有效结果。
有关更多详细信息,您应该查看编译代码时的汇编指令。请注意,一个C编译器可以选择为您提供不同的指令(因此结果不同)。
要专门回答您的问题需要汇编,但可能发生的是您的代码正在编译为LDR指令,该指令将您的地址舍入到最接近的2字节(偶数)地址。这是针对地址对齐的ARM5文档。我可以找到的在线参考是:http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0473m/dom1359731171041.html
根据该规范,[0]或[1]上的操作都应该映射到[0]上的操作,[2]或[3]上的操作都应该在[2]上产生操作, [4]或[5]上的操作都应该在[4]上产生操作,依此类推。如果您要使用32位值,则应将其向下舍入到可被4整除的最近地址。
请注意,C编译器可以将代码编译为完全不同的代码,产生不同的行为,并且仍然在规范内,即使从一个编译到下一个编译也是如此。
因此,虽然C结果未定义,但编译指令的结果未定义,并且结果汇编指令遵循该特定芯片的规范。