未定义行为
来自 cppreference.com
C 语言标准精确地指定了 C 语言程序的可观察行为,除了以下类别中的程序
- 未定义行为 - 对程序的行为没有限制。未定义行为的示例包括数组越界访问、有符号整数溢出、空指针解引用、在表达式中未指定顺序点的情况下对同一标量 多次修改、通过不同类型的指针访问对象等等。编译器不需要诊断未定义行为(尽管许多简单情况会被诊断),编译后的程序也不需要做任何有意义的事情。
- 未指定行为 - 允许两种或多种行为,并且实现不需要记录每种行为的效果。例如,求值顺序、相同的 字符串字面量 是否不同等等。每个未指定的行为都会产生一组有效结果中的一个结果,并且在同一个程序中重复执行时可能会产生不同的结果。
- 实现定义的行为 - 未指定的行为,其中每个实现都记录了如何做出选择。例如,字节中的位数,或者有符号整数右移是算术运算还是逻辑运算。
(注意:严格符合 程序不依赖任何未指定、未定义或实现定义的行为)
编译器必须针对违反任何 C 语法规则或语义约束的程序发出诊断消息(错误或警告),即使其行为被指定为未定义或实现定义的行为,或者编译器提供允许其接受此类程序的语言扩展。否则,不需要针对未定义行为的诊断消息。
内容 |
[编辑] UB 和优化
因为正确的 C 程序没有未定义的行为,所以当使用启用了优化的编译器编译实际上包含 UB 的程序时,可能会产生意想不到的结果
例如,
[编辑] 有符号溢出
int foo(int x) { return x + 1 > x; // either true or UB due to signed overflow }
可以编译为 (演示)
foo: mov eax, 1 ret
[编辑] 越界访问
int table[4] = {0}; int exists_in_table(int v) { // return 1 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 1; return 0; }
可以编译为 (演示)
exists_in_table: mov eax, 1 ret
[编辑] 未初始化的标量
可能会产生以下输出(在较旧版本的 gcc 中观察到)
p is true p is false
可以编译为 (演示)
f: mov eax, 42 ret
[编辑] 无效标量
int f(void) { _Bool b = 0; unsigned char* p = (unsigned char*)&b; *p = 10; // reading from b is now UB return b == 0; }
可以编译为 (演示)
f: mov eax, 11 ret
[编辑] 空指针解引用
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 = NULL; return *p; // Unconditional UB }
可以编译为 (演示)
foo: xor eax, eax ret bar: ret
[编辑] 对传递给 realloc 的指针的访问
选择 clang 观察显示的输出
运行此代码
可能的输出
12
[编辑] 无副作用的无限循环
选择 clang 观察显示的输出
运行此代码
#include <stdio.h> int fermat() { const int MAX = 1000; // Endless loop with no side effects is UB for (int a = 1, b = 1, c = 1; 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(void) { if (fermat()) puts("Fermat's Last Theorem has been disproved."); else puts("Fermat's Last Theorem has not been disproved."); }
可能的输出
Fermat's Last Theorem has been disproved.
[编辑] 参考
- C23 标准 (ISO/IEC 9899:2024)
- 3.4 行为 (p: TBD)
- 4 符合性 (p: TBD)
- C17 标准 (ISO/IEC 9899:2018)
- 3.4 行为 (p: 3-4)
- 4 符合性 (p: 8)
- C11 标准 (ISO/IEC 9899:2011)
- 3.4 行为 (p: 3-4)
- 4/2 未定义行为 (p: 8)
- C99 标准 (ISO/IEC 9899:1999)
- 3.4 行为 (p: 3-4)
- 4/2 未定义行为 (p: 7)
- C89/C90 标准 (ISO/IEC 9899:1990)
- 1.6 术语定义
[编辑] 外部链接
1. | 每个 C 程序员都应该了解的未定义行为 #1/3 |
2. | 每个 C 程序员都应该了解的未定义行为 #2/3 |
3. | 每个 C 程序员都应该了解的未定义行为 #3/3 |
4. | 未定义行为会导致时间旅行(以及其他事情,但时间旅行是最有趣的) |
5. | 理解 C/C++ 中的整数溢出 |
6. | 未定义行为和费马大定理 |
7. | 玩转 NULL 指针,第 1 部分(由于空指针解引用导致的 UB 导致的 Linux 2.6.30 中的本地漏洞) |
[编辑] 另请参阅
C++ 文档 中的 未定义行为
|