为什么指针算法在架构之间不一致?

时间:2016-10-13 02:53:34

标签: c pointers x86 arm byte

给出以下代码:

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

这两台机器都是小端的。

为什么会这样?

2 个答案:

答案 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结果未定义,但编译指令的结果未定义,并且结果汇编指令遵循该特定芯片的规范。