繁体   English   中英

即使 sizeof(void*)==sizeof(unsigned long int) 也可能将 void* 转换为 unsigned long int 导致未定义的行为

[英]Might casting void* into unsigned long int cause undefined behaviour even where sizeof(void*)==sizeof(unsigned long int)

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 同上。

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

忽略指针 vs integer 大小问题,由于严格的别名违规,这仍然是未定义的行为。 什么是严格的别名规则? void* object 所在的 memory 位置无法取消引用为unsigned long 这反过来又会导致在优化等过程中错误地生成机器代码,尤其是当代码被划分为多个翻译单元时。 所以这是有道理的担忧,而不仅仅是理论上的“语言律师”。

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

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

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

这是明确定义的——我们总是可以从指针转换为整数并返回。 但同样存在 object 大小以及 alignment 大小的问题 - 特别是在从整数到指针时。

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

除了相同的大小和 alignment 问题之外,这是有效的代码。 而C对指针的二进制表示没有任何要求,超出了标准的scope。

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

与 3) 相同的问题。

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

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

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

这是未定义的行为。 您正在使用unsigned long int类型访问void *值。 因为unsigned long intvoid*兼容,所以您打破了严格的别名。 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”object,则从该 object 读取执行陷阱 但是在当今的架构上,任何位模式都对unsigned long有效,因此它将导致一些实现定义的模式。

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

这等于ptr_b = case_3并且是有效的。

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM