3
size_t size_int = sizeof(unsigned long int);
size_t size_ptr = sizeof(void*);
printf("sizeof(unsigned long int): %zu\n", size_int);
printf("sizeof(void*): %zu\n", size_ptr);

if(size_int == size_ptr) {
    int a = 0;
    void * ptr_a = &a;
    
    // case 1
    unsigned long int case_1 = *((unsigned long int*)&ptr_a);
    printf("case 1: %lu\n", case_1);

    // case 2
    unsigned long int case_2 = (unsigned long int)ptr_a;
    printf("case 2: %lu\n", case_2);

    // case 3
    unsigned long int case_3 = 0;
    memcpy(&case_3, &ptr_a, sizeof(void*));
    printf("case 3: %lu\n", case_3);
    
    // case 4
    void *ptr_b = NULL;
    memcpy(&ptr_b, &case_3, sizeof(void*));
    int *ptr_c = (int*)ptr_b;
    *ptr_c = 5;
    printf("case 5: %i\n", a);
}

事实上,我知道 C99 中有 uintptr_t 和 intptr_t 。但是,出于教育目的,我想问一些问题。在开始之前,我知道这是一种不好的做法,绝不应该以这种方式进行。

Q1。案例 1 会导致未定义的行为吗?安全吗?如果不是,为什么?如果它是安全的,是否保证“case_1”变量与 unsigned long int 具有完全相同的地址?
Q2。与案例 2 相同。
Q3。与案例 3 相同。
Q4。案例 4 同上。

4

3 回答 3

3

unsigned long int case_1 = *((unsigned long int*)&ptr_a);

忽略指针与整数大小的关系,由于严格的别名违规,这仍然是未定义的行为。什么是严格的别名规则?对象所在的内存位置void*不能作为unsigned long. 这反过来又会导致在优化等过程中错误地生成机器代码,尤其是当代码被划分为多个翻译单元时。所以这是有道理的担忧,而不仅仅是理论上的“语言律师”。

由于对齐问题,至少在理论上,也可能存在未定义的行为。在实践中,如果指针和整数保持相同的大小,我真的不知道对齐将如何成为问题。

甚至在理论上,可能存在陷阱表示,或者在unsigned long(这又需要一个奇异的 1 的补码或有符号幅度系统),或者在指针类型本身。某些硬件可能对某些地址具有陷阱表示,理论上您可能会在此类系统上遇到硬件异常,尽管可能只是在从整数到指针时。

unsigned long int case_2 = (unsigned long int)ptr_a;

这是明确定义的——我们总是可以从指针转换为整数并返回。但是同样存在对象大小的问题,并且可能还存在对齐问题 - 特别是在从整数到指针时。

memcpy(&case_3, &ptr_a, sizeof(void*));

除了相同的大小和对齐问题,这是有效的代码。并且 C 对指针的二进制表示没有任何要求,这超出了标准的范围。

memcpy(&ptr_b, &case_3, sizeof(void*));

与 3) 相同的问题。

于 2020-07-02T08:25:38.240 回答
2

虽然在不同类型之间转换指针时可能会发生未定义的行为,但您可以将void *指针转换为/从任何其他指针类型。

case # 是未定义的行为吗?安全吗?如果不是,为什么?

unsigned long int case_1 = *((unsigned long int*)&ptr_a);

这是未定义的行为。您正在访问void *具有类型的值unsigned long int。因为与unsigned long int兼容void*,所以您正在打破严格的别名。见C11 6.5p7

unsigned long int case_2 = (unsigned long int)ptr_a;

这可能是未定义的行为。见C11 6.3.2.3p6。虽然它说它Any pointer type may be converted to an integer type也指出If the result cannot be represented in the integer type, the behavior is undefined。因此,在unsigned long具有 32 位但void *具有 64 位的架构上,这可能是未定义的行为。结果在任何情况下都是实现定义的。

 unsigned long int case_3 = 0;
 memcpy(&case_3, &ptr_a, sizeof(void*));

这显然是未定义的行为sizeof(void*) > sizeof(unsigned long)

 printf("case 3: %lu\n", case_3);

case_3当是陷阱表示时,这可能是未定义的行为。IE。如果 的内容case_3没有提供有效的“unsigned long int”对象,则从该对象读取会执行陷阱。但是在当今的架构上,任何位模式都对 有效unsigned long,因此它将导致一些实现定义的模式。

 memcpy(&ptr_b, &case_3, sizeof(void*));

这等于ptr_b = case_3并且有效。

于 2020-07-02T08:39:53.630 回答
1

C 语言参考(或更准确地说是 C11 的 n1570 草案)在 6.3.2.3 Conversions / Pointers §6 中说:

任何指针类型都可以转换为整数类型。除非前面指定,结果是实现定义的。如果结果不能以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

因此,即使sizeof(void*)==sizeof(unsigned long int)由于任何原因无法表示结果,它也可能是未定义的行为。原因可能是:

  • 类型中的填充位unsigned long导致更少的表示值
  • 导致不可表示的价值的病态转换

对于常见的体系结构(实际上我所知道的),将指针转换为无符号长整数会给出具有完全相同位的内存地址,并且在任何整数类型中都没有填充,因此不应该发生未定义的行为。但是这个标准非常保守……

于 2020-07-02T08:37:51.683 回答