未定义行为

来自cppreference.com
< cpp‎ | language

若违反某些规则,则令整个程序失去意义。

目录

[编辑] 解释

C++ 标准精确地定义不落入下列分类之一的可观察行为

  • 病式( ill-formed )——程序拥有语法错误或可诊断的予以错误。要求一致的 C++ 编译器发布诊断,即使它定义赋予这种代码含义的语言扩展(例如用非常量长度数组)。标准文本用 shall (应当)shall not (不应当)ill-formed (病式)指示这些要求。
  • 病式而不要求诊断( ill-formed no diagnostic required )——程序拥有通常情况下可能无法诊断的语义错误(例如 ODR 的违规或者其他只能在链接时检测的错误)。若执行这种程序则行为未定义。
  • 实现定义行为( implementation-defined behavior )——程序的行为在实现间变动,而一致的实现必须为每个行为的效果提供文档。例如 std::size_t 的类型或字节中的位数,或 std::bad_alloc::what 的文本。实现定义行为的一个子集是本地环境特定行为( locale-specific behavior ),它取决于实现所提供的本地环境
  • 未指定行为( unspecified behavior )——程序的行文在实现间变动,而不要求一致的实现对每个行为的效果提供文档。例如求值顺序、等同的字符串字面量是否为相异对象、数组分配的开销等。每个未指定行为产生合法结果集合中的一个结果。
  • 未定义行为( undefined behavior )——程序行为上无限制。未定义行为的例子是数组边界外的内存访问、有符号整数溢出、空指针解引用、在无序列点的表达式中修改同一标量多于一次、通过不同类型的指针访问对象等。不要求编译器诊断未定义行为(尽管许多简单情形会得到诊断),且不要求编译的程序做任何有意义的事。

[编辑] UB 与优化

因为正确的 C++ 程序不含未定义行为,故在启用优化选项以编译实际上有 UB 的程序时,编译器可能产生不期待的结果:

例如,

[编辑] 有符号溢出

int foo(int x) {
    return x+1 > x; // true 或有符号溢出所致的 UB
}

可编译为(演示

foo(int):
        movl    $1, %eax
        ret


[编辑] 边界外访问

int table[4] = {};
bool exists_in_table(int v)
{
    // 在头 4 次迭代中返回 true 或边界外访问所致的 UB
    for (int i = 0; i <= 4; i++) {
        if (table[i] == v) return true;
    }
    return false;
}

可能编译为(演示

exists_in_table(int):
        movl    $1, %eax
        ret

[编辑] 未初始化标量

std::size_t f(int x)
{
    std::size_t a;
    if(x) // x 非零或 UB
        a = 42;
    return a; 
}

可能编译为(演示

f(int):
        mov     eax, 42
        ret

在旧版本 gcc 上观察到展示的输出

bool p; // 未初始化局部变量
if(p) // UB :访问未初始化标量
    std::puts("p is true");
if(!p) // UB :访问未初始化标量
    std::puts("p is false");

可能的输出:

p is true
p is false

[编辑] 非法标量

int f() {
  bool b = true;
  unsigned char* p = reinterpret_cast<unsigned char*>(&b);
  *p = 10;
  // 从 b 读取现在是 UB
  return b == 0;
}

可编译成(演示

f():
        movl    $11, %eax
        ret

[编辑] 空指针解引用

int foo(int* p) {
    int x = *p;
    if(!p) return x; // 上述 UB 或决不采用此分支
    else return 0;
}
int bar() {
    int* p = nullptr;
    return *p;        // 无条件 UB
}

可能编译为( foo 用 gccbar 用 clang

foo(int*):
        xorl    %eax, %eax
        ret
bar():
        retq

[编辑] 访问传递给 realloc 的指针

选择 clang 以观察所示输出

#include <iostream>
#include <cstdlib>
int main() {
    int *p = (int*)std::malloc(sizeof(int));
    int *q = (int*)std::realloc(p, sizeof(int));
    *p = 1; // UB :访问传递给 realloc 的指针
    *q = 2;
    if (p == q) // UB :访问传递给 realloc 的指针
        std::cout << *p << *q << '\n';
}

可能的输出:

12

[编辑] 无副效应的无限循环

选择 clang 以观察所示输出

#include <iostream>
 
int fermat() {
  const int MAX = 1000;
  int a=1,b=1,c=1;
  // 无副效应的无限循环是 UB
  while (1) {
    if (((a*a*a) == ((b*b*b)+(c*c*c)))) return 1;
    a++;
    if (a>MAX) { a=1; b++; }
    if (b>MAX) { b=1; c++; }
    if (c>MAX) { c=1;}
  }
  return 0;
}
 
int main() {
  if (fermat())
    std::cout << "Fermat's Last Theorem has been disproved.\n";
  else
    std::cout << "Fermat's Last Theorem has not been disproved.\n";
}

可能的输出:

Fermat's Last Theorem has been disproved.

[编辑] 外部链接

[编辑] 参阅

未定义行为C 文档