代码之家  ›  专栏  ›  技术社区  ›  anon

如何在C程序中鼓励未定义的行为/无序执行?

  •  0
  • anon  · 技术社区  · 5 年前

    我正在阅读关于C中序列点的以下文章: https://www.geeksforgeeks.org/sequence-points-in-c-set-1/

    其中,有几个未定义行为的示例,例如调用两个修改单个全局变量的函数的表达式,或多次递增同一变量的单个表达式。

    理论上,我理解这个概念。然而,无论我试着运行多少次示例,行为都是一样的,从不“令人惊讶”

    为了获得对未定义行为的亲身体验,让这些例子“出人意料”的最简单方法是什么?

    (如果重要的话,我正在使用MINGW64。)

    1 回复  |  直到 5 年前
        1
  •  0
  •   dgnuff    5 年前

    这是我能在短时间内想到的最好的办法:

    源代码:

    #include <stdio.h>
    
    int undefined(int *a, short *b)
    {
        *a = 1;
        b[0] = 0;
        b[1] = 0;
        return *a;
    }
    
    int main()
    {
        int x;
        short *y = (short*) &x;
        int z = undefined(&x, y);
        printf("%d\n", z);
        return 0;
    }
    

    使用gcc 8.3-O3进行组装

    undefined(int*, short*):
        mov     DWORD PTR [rdi], 1
        mov     eax, 1
        mov     DWORD PTR [rsi], 0
        ret
    .LC0:
        .string "%d\n"
    main:
        sub     rsp, 8
        mov     esi, 1
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        call    printf
        xor     eax, eax
        add     rsp, 8
        ret
    

    行动起来: https://godbolt.org/z/E0XDYt

    特别是,它依赖于由强制转换 int 给一个 short* ,此操作会破坏严格的别名规则,从而导致未定义的行为。

    从组装开始 undefined() . 假设自从 a b 是不同的类型,它们不能重叠,因此它优化了 return *a; 进入之内 mov eax,1 ,即使它从内存中获取值时实际上会返回零。这是一个非常隐蔽的问题,它只出现在优化的发布版本中,而不是用非优化的调试版本来调试它。

    但是,请注意 main() 是否尝试使其正确:它内联,然后优化对 未定义() 而是假设 0 在里面 z 当它做的时候 xor eax,eax 就在电话的上方 printf . 所以它忽略了上面几行的返回值,而是使用了一个不同的值。

    总而言之,一个非常糟糕的程序。正是你用未定义的行为所冒的风险。

        2
  •  0
  •   supercat    5 年前

    测试gcc和clang时,一个有用的模式是使用下标访问数组,这些下标的值在边界内,但编译器不知道,并且使用标准描述为等同于下标符号的指针语法测试gcc和clang,比如:

    struct S1 {int x;};
    struct S2 {int x;};
    
    union foo { struct S1 arr1[8]; struct S2 arr2[8]; } u;
    
    uint32_t test1(int i, int j)
    {
      if (sizeof u.arr1 != sizeof u.arr2)
        return -99;
      if (u.arr1[i].x)
        u.arr2[j].x = 2;
      return u.arr1[i].x;
    }
    uint32_t test2(int i, int j)
    {
      if (sizeof u.arr1 != sizeof u.arr2)
        return -99;
      if ((u.arr1+i)->x)
        (u.arr2+j)->x = 2;
      return (u.arr1+i)->x;
    }
    

    将揭示尽管标准定义了 u.arr1[i].x u.arr2[j].x 相当于 (u.arr1+i)->x (u.arr2+j)->x ,gcc和clang分别在给定前者时错过了一个允许的优化机会,而在给定后者时则利用了前者这很可能是因为作者认识到,利用以前的机会是允许的,但不可否认的是,这样做是愚蠢的,以至于不得不承认,该标准从未打算鼓励它允许的所有优化。

    推荐文章