在32位嵌入式系统上投射和修改指针时遇到了一个奇怪的问题(redbee econotag运行contiki OS是特定的)。
uint32_t array[2];
array[0] = 0x76543210;
array[1] = 0xfedcba98;
uint8_t* point = ((uint8_t*)array)+1;
printf("%08x \n", *(uint32_t*)point );
在我的电脑上输出:
98765432
嵌入式设备上的输出:
10765432
我的计算机的行为与我预期的一样,但嵌入式设备似乎在到达单词结尾时会回滚。为什么会这样?
答案 0 :(得分:13)
使用此代码,您将破坏严格别名规则:point
指向的对象由具有uint32_t
类型的左值表达式访问。
C11(n1570),§6.5表达式
对象的存储值只能由具有以下类型之一的左值表达式访问:
- 与对象的有效类型兼容的类型,
- 与对象的有效类型兼容的类型的合格版本,
- 对应于有效类型的有符号或无符号类型的类型 对象,
- 对应于合格版本的有符号或无符号类型的类型 有效的对象类型,
- 聚合或联合类型,其中包括上述类型之一 成员(包括递归地,子集合或包含的联合的成员)或
- 字符类型。
这会导致未定义的行为,因此任何事情都可能发生。
C11(n1570),§4。一致性
如果违反约束或运行时约束之外的“应该”或“不应该”的要求,则行为未定。
答案 1 :(得分:13)
您的目标“redbee econotag”被声明为具有ARMv4架构的ARM7。 ARMv4不提供像ARMv7或intel机器那样的未对齐内存访问。
在ARMv4和ARMv5体系结构以及ARMv6体系结构(取决于其配置方式)上,在访问内存中的未对齐数据时需要小心,以免返回意外结果。例如,当传统指针用于读取C或C ++源代码中的单词时,ARM编译器生成使用LDR指令读取单词的汇编语言代码。当地址是四的倍数时,这可以正常工作,例如,如果它位于单词边界上。但是,如果地址不是4的倍数,则LDR返回旋转结果,而不是执行真正的未对齐字加载。通常,这种轮换不是程序员所期望的。
答案 2 :(得分:6)
printf("%08x \n", *(uint32_t*)point );
此语句中的*
表达式调用未定义的行为:它违反了别名规则,可能会进行未对齐的访问。
答案 3 :(得分:6)
由于+1
您对32位值进行了未对齐访问,即地址不是4的倍数。
x86独立于对齐工作,因为它的根源一直追溯到8位机器(可能性能稍差)。
ARM需要对齐(与许多其他处理器一样),因此应将32位值放在四个字节的倍数的地址上。如果不是这种情况(错误的值,错误),可能会发生各种不好的事情。对于数组,编译器会处理这个问题,但是当您显式地转换指针时,会强制它违反对齐。
答案 4 :(得分:2)
编辑:请注意,此答案的正文与其提示的评论无关
其他答案的理论很好,但可能对你没有帮助。实际问题是你写的:
uint8_t* point = ((uint8_t*)array)+1;
当你应该写一些诸如
之类的东西时uint8_t* point = (uint8_t*)(array+1);
因为你需要将指针增加为指向适当类型的指针(以便增量操作将添加元素的大小),然后再将其转换为其他类型。
但有人可能会问你是否真的想要一个指向32位值的字节指针。也许你打算以字节方式访问它(请注意字节顺序会因系统而异!)。或者你真的打算指向一个32位值的指针,而这个指针又指向其他地方的8位值......