C++ 未定义的行为

  • UB(Undefined behavior):程序的行为没有限制。常见的未定义行为例子包括:数组访问内存越界,有符号数溢出,间接访问空指针,在一个表达式中无顺序多次修改标量,用不同类型的指针访问对象
  • UB and optimization

    • 正确的 C++ 程序是没有未定义行为的,所以当编译器优化了含有 UB 的代码,程序会出现不可预料的结果
    • 有符号数溢出,下面是 C++ 代码和对应可能生成的机器语言
    • 没有溢出时返回 1,溢出时是 UB,编译器可能优化,每次都返回 1

      int foo(int x)
      {
      return x+1 > x; // either true or UB due to signed overflow
      }
      
      foo(int):
      movl    $1, %eax
      ret
      
    • 访问越界,下面是 C++ 代码和对应可能生成的机器语言

    • 访问下标在 0-3 时,如果存在元素 v 返回true,否则会访问越界,编译器可能优化,每次都返回 true,也不会访问越界

      int table[4] = {};
      bool exists_in_table(int v)
      {
      // return true in one of the first 4 iterations or UB due to out-of-bounds access
      for (int i = 0; i <= 4; i++)
      {
          if (table[i] == v) return true;
      }
      return false;
      }
      
      exists_in_table(int):
      movl    $1, %eax
      ret
      
    • 未初始化的标量,下面是 C++ 代码和对应可能生成的机器语言

    • 当 x 非 0 时,a 会被赋值 42,否则 a 未初始化,编译器可能优化,每次都将 a 赋值42,然后返回

      std::size_t f(int x)
      {
      std::size_t a;
      if(x) // either x nonzero or UB
          a = 42;
      return a;
      }
      
      f(int):
      mov     eax, 42
      ret
      
    • 间接访问空指针,下面是 C++ 代码和对应可能生成的机器语言

    • 函数foo:当 p 是空指针时,x 的赋值是间接访问空指针。否则返回 0。编译器可能优化,每次返回 0 而不会访问到空指针

      • xorl %eax,%eax按位异或,相当于清 0,将寄存器%eax设置为 0。也可以使用movl $0,%eax,但是前者需要 2 个字节,后者需要 5 个字节
    • 函数bar:直接访问空指针指向的值是 UB,编译器可能优化,每次直接执行下一行代码

      • retq等同于addq $8,%rsp; jmpq -8(%rsp)retq%esp指向的返回地址弹出,存入寄存器%eip
      • 寄存器%eip是程序计数器,存储了 CPU 要读取指令的地址,即 CPU 将要执行的指令的地址。每次 CPU执行完相应的汇编指令后,%eip的值就会增加
      • 寄存器%esp是栈指针指向栈顶元素。栈向低地址方向增长,可以通过增加栈指针来释放空间
      • 函数调用时会先将返回地址入栈,即程序中紧跟在调用函数后面的那条指令的地址,所以栈顶指针%esp指向的就是调用函数后面的那条指令的地址,retq会将该地址存入%eip,CPU 就会继续往后执行
      • 在 64-bit 时,ret会从栈中弹出四字节的地址保存到寄存器%eip
      • 在 32-bit 时,ret会从栈中弹出两字节的地址保存到寄存器%eip

        int foo(int* p)
        {
        int x = *p;
        if(!p) return x; // Either UB above or this branch is never taken
        else return 0;
        }
        int bar()
        {
        int* p = nullptr;
        return *p;        // Unconditional UB
        }
        
        foo(int*):
        xorl    %eax, %eax
        ret
        bar():
        retq
        

相关