看代码:
#include <stdio.h>
#include <stdint.h>
int main() {
char foo[512]={};
printf("%d", *((uint32_t*)foo));
return 0;
}
我很难理解
*((uint32_t*)foo))
的作用,在数组中使用不同的值我得到了各种返回值。
它究竟指向什么,返回值又是多少?
char foo[512]={};
是无效语法,C 中不允许使用空的初始化列表。如果要初始化它,则必须使用 {0}
。(uint32_t*)foo
是可疑的,因为 uint32_t*
不一定与 char*
兼容。此外,char
数组可能未对齐。 1)
经验法则是我们可以将从任何对象指针类型转换为字符指针类型,但不能反过来。*((uint32_t*)foo)
以多种方式调用未定义的行为。 foo
可能错位。而且它也是严格的指针别名违规(什么是严格的别名规则?)。 TL;DR 基本上是编译器可以自由地假设 char
数组从未在您发布的代码中使用,因为它可以自由地假设 char
永远不会通过 uint32_t*
.忽略以上所有内容——我们不应该这样做,因为未定义的行为意味着任何事情都可能发生——那么编译器很可能(但不保证)会从
char
数组中获取 4 个字节并将它们重新解释为 uint32_t
.假设 char
是 8 位,那么它(可能)会根据 CPU 的字节顺序这样做。也就是说,如果我们执行 char foo[512]={'A','B','C','D'};
并且 CPU 有小端格式,那么 'D'
将以 uint32_t
的最低字节结束。 什么是 CPU 字节顺序? 所以使用 ASCII,它会变成数字 0x44434241
.
请注意,
%d
是打印uint32_t
的错误格式说明符。您应该使用%u
或printf("%"PRIu32, ...)
中最正确的形式inttypes.h
。
1)C17 6.3.2.3是C标准中的相关规则:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果 结果指针未针对引用类型正确对齐,行为是 不明确的。否则,当再次转换回来时,结果应比较等于 原始指针。
*((uint32_t*)foo)
基本上将 foo 数组转换为 uint32_t
数组,这是一个 4 字节大小的无符号整数数组。
您正在将一个 char 数组(通常每个元素 1 个字节)转换为每个元素 4 个字节的数组。然后你取消引用它,这意味着你正在尝试从该数组中读取 4 个字节。
对于以下代码:
int main() {
char arr[] = "\xcc\xcc\xcc\xcc";
printf("My result: %x", *((uint32_t*)arr));
return 0;
}
输出将是:
My result: cccccccc
请注意,您应该考虑编译中使用的endians。 大多数系统出于效率目的(例如转换)而使用小端,如果您在以下代码的编译中使用小端:
int main() {
char arr[] = "\x11\x22\x33\x44";
printf("My result: %x", *((uint32_t*)arr));
return 0;
}
输出将是
44332211
而不是 11223344
这可能看起来更微不足道。
它被称为“指针双关语”。它用于将一种类型的二进制表示重新解释为另一种类型。它调用“未定义的行为”
UB
并且必须避免
更好(和安全)的方法是使用
memcpy
功能。在许多情况下,现代优化编译器将优化 memcpy
调用。
例子:
uint32_t charAsuint32(const char *charr)
{
uint32_t result;
memcpy(&result, charr, sizeof(result));
return result;
}
和生成的代码:
charAsuint32:
mov eax, DWORD PTR [rdi]
ret
PS 你使用了错误的格式来显示
uint32_t
值 - 它也是一个 UB。
*((uint32_t*)foo))
在此表达式中,
foo
被类型转换为 uint32_t
指针,然后取消引用。
将变量从一种类型的指针转换为另一种类型通常违反严格的别名规则¹,并且
*((uint32_t*)foo))
正是这样做的。所以表达式调用未定义的行为。
此外,
foo
可能没有正确对齐:
来自C11:
1 在以下情况下行为未定义:
....
两个指针类型之间的转换产生的结果是 对齐不正确 (6.3.2.3)
未对齐数据是地址(又名指针值)处的数据不能被其对齐(通常是其大小)整除。
注意,空的初始化列表在 C23 之前是无效的,只是因为
int32_t
在您的编译器/平台上恰好是 int
并不意味着它在另一个平台上可能不是 long
。
%d
不是 int32_t
的正确格式说明符。如果您不想对固定宽度的整数类型使用特定的宏,另一种方法是转换为 intmax_t
/ uintmax_t
并分别使用 %jd
和 %ju
。
1
参见:什么是严格的别名规则?