【问题标题】:Might casting void* into unsigned long int cause undefined behaviour even where sizeof(void*)==sizeof(unsigned long int)即使 sizeof(void*)==sizeof(unsigned long int) 也可能将 void* 转换为 unsigned long int 导致未定义的行为
【发布时间】:2020-07-02 08:06:35
【问题描述】:
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。但是,出于教育目的,我想问一些问题。在开始之前,我知道这是一种不好的做法,绝不应该以这种方式进行。

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

【问题讨论】:

    标签: c type-conversion pointer-conversion


    【解决方案1】:

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

    忽略指针与整数大小的关系,由于严格的别名违规,这仍然是未定义的行为。 What is the strict aliasing rule? 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) 中相同的问题。

    【讨论】:

      【解决方案2】:

      虽然undefined behavior may happen when casting pointers between different types 允许您将void * 指针转换为/从任何其他指针类型。

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

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

      这是未定义的行为。您正在访问具有 unsigned long int 类型的 void * 值。因为unsigned long int 不是compatiblevoid*,所以你打破了严格的别名。见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_3trap representation 时,这可能是未定义的行为。 IE。如果case_3 的内容没有提供有效的“unsigned long int”对象,则从该对象performs a trap 中读取。但在当今的架构中,任何位模式都对unsigned long 有效,因此会产生一些实现定义的 模式。

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

      这等于ptr_b = case_3并且有效。

      【讨论】:

        【解决方案3】:

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

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

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

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

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

        【讨论】:

          猜你喜欢
          • 2016-12-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多