*((uint32_t*)foo)调用未定义的行为,可能有几种方式。foo可能未对齐。它也是一个严格的指针别名违规(What is the strict aliasing rule?)。TL;DR基本上是编译器可以自由地假设char数组从未在您发布的代码中使用,因为它可以自由地假设char永远不会通过uint32_t*访问。
忽略上述所有情况--我们不应该这样做,因为未定义的行为意味着任何事情都可能发生--那么很可能(但不保证)编译器将从char数组中抓取4个字节并将其重新解释为uint32_t。也就是说,如果我们执行char foo[512]={'A','B','C','D'};并且CPU具有小字节序格式,那么'D'将结束在uint32_t.What is CPU endianness?的最低字节中。它将变成数字0x44434241。 请注意,%d是打印uint32_t的错误格式说明符。您应该使用%u或inttypes.h中最正确的格式printf("%"PRIu32, ...)。 1)C176.3.2.3是C标准中的相关规则: 指向一个对象类型的指针可以转换为指向另一个对象类型的指针。如果结果指针没有正确对齐引用的类型,则行为未定义。否则,当再次转换回来时,结果将与原始指针进行比较。
3条答案
按热度按时间2vuwiymt1#
char foo[512]={};
是无效的语法,C中不允许空的初始化列表。如果你想初始化它,你必须使用{0}
。(uint32_t*)foo
是可疑的,因为uint32_t*
不一定与char*
兼容。此外,char
数组可能没有对齐。1)经验法则是,我们可以将从任何对象指针类型转换为*字符指针类型,但不能反过来。*((uint32_t*)foo)
调用未定义的行为,可能有几种方式。foo
可能未对齐。它也是一个严格的指针别名违规(What is the strict aliasing rule?)。TL;DR基本上是编译器可以自由地假设char
数组从未在您发布的代码中使用,因为它可以自由地假设char
永远不会通过uint32_t*
访问。忽略上述所有情况--我们不应该这样做,因为未定义的行为意味着任何事情都可能发生--那么很可能(但不保证)编译器将从
char
数组中抓取4个字节并将其重新解释为uint32_t
。也就是说,如果我们执行char foo[512]={'A','B','C','D'};
并且CPU具有小字节序格式,那么'D'
将结束在uint32_t
.What is CPU endianness?的最低字节中。它将变成数字0x44434241
。请注意,
%d
是打印uint32_t
的错误格式说明符。您应该使用%u
或inttypes.h
中最正确的格式printf("%"PRIu32, ...)
。1)C176.3.2.3是C标准中的相关规则:
指向一个对象类型的指针可以转换为指向另一个对象类型的指针。如果结果指针没有正确对齐引用的类型,则行为未定义。否则,当再次转换回来时,结果将与原始指针进行比较。
e5nszbig2#
在这个表达式中,
foo
被类型转换为uint32_t
指针,然后被解引用。将变量从一种类型的指针转换为另一种类型通常违反严格的别名规则¹,并且
所以表达式调用了未定义的行为。
此外,
foo
可能未正确对齐:C11:
1在以下情况下,行为未定义:
....
两种指针类型之间的转换会产生不正确对齐的结果(6.3.2.3)
6.3.2.3p7表示
[...]如果结果指针没有正确对齐[68]引用的类型,则行为未定义。[...]
未对齐的数据是地址(指针值)上的数据,它不能被其对齐方式(通常是其大小)整除。
注意,空的初始化列表在C23之前是无效的,仅仅因为
int32_t
在你的编译器/平台上碰巧是int
并不意味着它在另一个编译器/平台上可能不是long
。%d
不是int32_t
的正确格式说明符。如果您不想使用固定宽度整数类型的特定宏,另一种方法是转换为intmax_t
/uintmax_t
并分别使用%jd
和%ju
。脚注:
1
参见:What is the strict aliasing rule?
oyjwcjzk3#
它被称为***“指针双关”***。它用于将一种类型的二进制表示重新解释为另一种类型。它调用“未定义行为”
UB
,必须避免一个更好(也更安全)的方法是使用
memcpy
函数。现代优化编译器在很多情况下都会优化memcpy
的调用。示例:
生成的代码:
PS你使用了错误的格式来显示
uint32_t
值-而且它也是一个UB。您的示例:
https://godbolt.org/z/E1eTc8djj