如您所见,我只是获取一个变量地址并通过其他variables 和pointers 来回获取它。通过 printf 我可以看到 address1、value2 和 address 2 都持有相同的 value。但是,当我尝试取消引用 address2(与 address1 和 &value 具有相同的 value)时,我得到了错误。为什么?
#include <stdio.h>
#include <stdint.h>
int main()
{
uint32_t value = 0;
uint32_t* address1 = NULL;
uint32_t* address2 = NULL;
address1 = &value;
printf("address1: %x\n", address1);
uint32_t value2 = (uint32_t)address1;
printf("value2: %x\n", value2);
address2 = (uint32_t*)value2;
printf("address2: %x\n", address2);
uint32_t value3 = *address2; //fault here
printf("*value2: %u\n", value3);
return 0;
}
回答1
在 64 位系统上,uint32_t
不足以容纳指针。无论如何,如果在其中放置一个,地址的上半部分就会被截断,因此当您随后使用它时,它会指向错误的位置。更改为 uintptr_t
以修复它。
此外,正如 Ingo Leonhardt 评论的那样,%x
是 pointers 的错误格式说明符,因此它们的打印形式也被截断,这就是为什么您认为 values 都是相等的,即使它们实际上并非如此。
回答2
Joseph 的回答解释了为什么该程序在您的特定设置(32 位与 64 位)上失败。我将尝试给出一个仅基于 C 标准(主要是 C99)且与平台无关的答案。
问题:您的程序具有未定义的行为,因为它违反了 C 标准。
具体来说,问题在于以下几行的组合:
uint32_t* address1 = NULL; // 1
uint32_t value2 = (uint32_t)address1; // 2
address2 = (uint32_t*)value2; // 3
在第 2 行,您将“指向 uint32_t
的指针”(uint32_t*
) 转换为 uint32_t
。这本身没问题 - 您将获得指针的数字表示,可能会被截断以适合 unit32_t
。
但是,在第 3 行中,您继续将 value2
转换回 uint32_t*
,并取消引用它。这是未定义的行为,因为(根据 C 标准)不能保证生成的 address2
是有效指针。
原则上,您可以将有效指针强制转换为整数类型并返回,并返回相同的(有效)指针。但是,这仅在您使用的整数类型对于指针而言足够大时才有效 - 为确保这一点,您需要使用 uintptr_t
,它专门用于此目的。如果您的平台没有 uintptr_t
(仅在 C99 中引入),您将需要特定于平台的类型。
参见例如https://stackoverflow.com/questions/4810417/when-is-casting-between-pointer-types-not-undefined-behavior-in-c 了解更多详细信息,包括指针types 之间以及整数和pointers 之间的转换。
至于如何避免这些问题:没有通用的解决方案,但现代编译器会在很多情况下尝试警告您。例如,在我的系统(64 位,GCC 7.5.0)上,gcc(即使没有任何警告选项)将警告上面第 3 行中的有问题的指针转换:
st.c: In function ‘main’: tst.c:13:23: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] uint32_t value2 = (uint32_t)address1; ^
它还会警告第二个演员:
tst.c:16:16: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] address2 = (uint32_t*)value2; ^
最后,它甚至会注意到约瑟夫的回答中提到的错误 printf
格式字符串:
tst.c:17:24: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘uint32_t * {aka unsigned int *}’ [-Wformat=] printf("address2: %x\n", address2); ~^ %ls
教训:始终使用 -Wall -Wextra
(或编译器中任何打开警告的开关)进行编译,并注意警告。