c - 将 uint8_t* 转换为 uint32_t* 行为

c - casting uint8_t* to uint32_t* behaviour

我读过这个问题:但我不确定给出的答案。

我是新手嵌入式 C 程序员,正在从事一个使用 GCC 的项目,我一直在重构代码块以减少内存使用。一个例子是我将一些变量的数据类型从 uint32_t 更改为更小的类型:

uint8_t colour;
uint16_t count;
uint16_t pixel;

func((uint32_t*)&colour);
func((uint32_t*)&count);
func((uint32_t*)&pixel);

其中 func(uint32_t* ptr) 修改传递给它在从属端口上接收的数据的值。我遇到一个问题,当启用 -O1-O2-O3-Os 优化时,上述代码无法正常运行。例如,当通信端口和函数分别收到值 1 5 1 时,为变量设置的值为 0 0 1。未启用优化时,值设置正确。

如果我将数据类型改回 uint32_t,代码将正常运行。我不明白为什么我没有从编译器收到任何警告(我打开了额外的警告)。发生这种情况的原因与 endianess/allignment 有关吗?

uint8_t 指针向上转换为 uint32_t 指针的陷阱是什么?

TLDR

uint8_t 的地址传递给需要 uint32_t 地址的东西可能会导致内存损坏、未知结果、细微错误和直接崩溃的代码。

详情

首先,如果函数声明为

void func( uint32_t * arg );

并修改 arg 指向的数据,将 uint8_tuint16_t 的地址传递给它会导致 undefined behavior 并可能导致数据损坏 - 如果它运行s(继续阅读...)。该函数将修改实际上不是传递给该函数的指针所指对象的一部分的数据。

该函数预期可以访问 uint32_t 的四个字节,但您只给了它一个 uint8_t 字节的地址。其他三个字节去哪儿了?他们可能会踩到别的东西。

并且即使该函数只读取内存而不修改它,您也不知道内存中有什么而不​​是实际对象中的内容,因此该函数的行为可能无法预测。而且阅读可能根本不起作用(继续阅读......)。

此外,强制转换 uint8_t 的地址是一种严格的别名违规行为。参见 What is the strict aliasing rule?。总而言之,在 C 中,您不能安全地将对象引用为它不是的对象,唯一的例外是您可以引用任何对象,就好像它是由适当数量的 [signed|unsigned] char 字节组成的一样。

但是将 uint8_t 地址转换为 uint32 * 意味着您正在尝试访问一组四个 unsigned char 值(假设 uint8_t 实际上是 unsigned char],现在几乎可以肯定)作为单个 uint32_t 对象,这是严格的别名违规,未定义的行为,而且不安全。

您因违反严格的别名规则而看到的症状可能很微妙,而且很难找到和修复。请参阅 gcc, strict-aliasing, and horror stories 了解一些恐怖故事。

此外,如果您将一个对象称为它不是的东西,您可以 运行 与 6.3.2.3 Pointers, paragraph 7 of the C (C11) standard:

相冲突

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined.

如果你听到有人说,"Well, it works so that's all wrong.",那么,他们是非常非常错误的。

他们只是没有观察到它失败了。

还有。