c - 将变量地址分配给指针。取消引用指针会导致分段错误。如何?

如您所见,我只是获取一个变量地址并通过其他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 (或编译器中任何打开警告的开关)进行编译,并注意警告。

相似文章