7

我在 32 位嵌入式系统上转换和修改指针时遇到了一个奇怪的问题(具体来说是运行contiki OS的redbee econotag)。

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

我的计算机的行为与我预期的一样,但是当嵌入式设备到达单词末尾时,它似乎会环绕。为什么会这样?

4

5 回答 5

13

您的目标“redbee econotag”被声明为具有 ARMv4 架构的 ARM7。ARMv4 不像 ARMv7 或英特尔机器那样提供未对齐的内存访问。

引用ARM 的文档

在 ARMv4 和 ARMv5 架构上,以及在 ARMv6 架构上,取决于其配置方式,在访问内存中未对齐的数据时需要小心,以免返回意外结果。例如,当使用常规指针读取 C 或 C++ 源代码中的字时,ARM 编译器生成汇编语言代码,该代码使用 LDR 指令读取字。当地址是四的倍数时,这可以按预期工作,例如,如果它位于字边界上。但是,如果地址不是 4 的倍数,LDR 将返回旋转结果,而不是执行真正的未对齐字加载。通常,这种轮换不是程序员所期望的

于 2012-12-26T02:14:47.233 回答
13

使用此代码,您打破了严格的别名规则:指向的对象由point具有uint32_t类型的左值表达式访问。

C11 (n1570), § 6.5 表达式
对象的存储值只能由具有以下类型之一的左值表达式访问:
— 与对象的有效类型兼容的类型,
— 与对象的有效类型,
— 对应于对象有效类型的有符号或无符号类型,
— 对应于对象有效类型的限定版本的有符号或无符号类型,
—聚合或联合类型,在其成员中包含上述类型之一(递归地包括子聚合或包含联合的成员),或者
- 字符类型。

这会导致未定义的行为,因此任何事情都可能发生。

C11 (n1570), § 4. Conformance
如果违反了出现在约束或运行时约束之外的“应该”或“不应”要求,则行为未定义。

于 2012-12-25T16:53:12.260 回答
6

由于+1您对 32 位值进行非对齐访问,即地址不是四的倍数。

x86 独立于对齐工作,因为它的根源可以追溯到 8 位机器(可能性能稍差)。

ARM 需要对齐(与许多其他处理器一样),因此 32 位值应放置在四字节的倍数处。如果不是这种情况,可能会发生各种不好的事情(错误的值、错误)。对于数组,编译器会处理这一点,但是当您显式转换指针时,您会强制它违反对齐方式。

于 2012-12-25T21:34:03.153 回答
5
printf("%08x \n", *(uint32_t*)point );

此语句中的*表达式调用未定义的行为:它违反了别名规则并且可能进行未对齐的访问。

于 2012-12-25T16:53:03.233 回答
2

编辑:请注意,这个答案的正文与它提示的评论无关

其他答案的理论很好,但可能对你没有帮助。实际的问题是您写道:

uint8_t* point = ((uint8_t*)array)+1;

当你应该写一些东西时,比如

uint8_t* point = (uint8_t*)(array+1);

因为您需要将指针递增为指向适当类型的指针(以便递增操作将添加元素的大小),然后再将其转换为其他内容。

但有人可能会问,您是否真的打算拥有一个指向 32 位值的字节指针。也许您确实打算以字节方式访问它(请注意字节顺序会因系统而异!)。或者,也许您真的打算让 point 成为指向 32 位值的指针,而该指针又是指向其他地方的 8 位值的指针……

于 2012-12-25T20:59:57.230 回答