C
C 语法

Undefined behavior

Undefined behavior

C语言标准精确地规定了C语言程序的可观察行为,除了以下几类:

  • 未定义的行为 - 对程序的行为没有限制。未定义行为的例子是数组边界之外的内存访问,有符号整数溢出,空指针取消引用,在没有序列点的表达式中多次修改相同标量,通过不同类型的指针访问对象等。编译器不需要诊断未定义的行为(虽然诊断了许多简单情况),编译后的程序不需要做任何有意义的事情。

(注意:严格符合的程序不依赖于任何未指定的,未定义的或实现定义的行为)。

编译器需要为任何违反C语法规则或语义约束的程序发布诊断消息(错误或警告),即使其行为被指定为未定义或实现定义的,或者编译器提供了允许它的语言扩展接受这样的计划。对未定义行为的诊断不是必需的。

UB和优化

由于正确的C程序没有未定义的行为,因此编译器可能会在实际具有UB的程序在启用优化的情况下编译时产生意外的结果:

例如,

签名溢出

int foo(int x) { return x+1 > x; // either true or UB due to signed overflow }

可能被编译为(演示)。

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

访问出界

int table[4] = {}; int 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 1; } return 0; }

可以编译为(演示)。

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

未初始化的标量

bool p; // uninitialized local variable if(p) // UB access to uninitialized scalar puts("p is true" if(!p) // UB access to uninitialized scalar puts("p is false"

可能产生以下输出(用旧版本的gcc观察):

p is true p is false

size_t f(int x) { size_t a; if(x) // either x nonzero or UB a = 42; return a; }

可以编译为(演示)。

f(int): mov eax, 42 ret

访问传递给realloc的指针

选择clang以观察显示的输出。

#include <stdio.h> #include <stdlib.h> int main(void) { int *p = (int*)malloc(sizeof(int) int *q = (int*)realloc(p, sizeof(int) *p = 1; // UB access to a pointer that was passed to realloc *q = 2; if (p == q) // UB access to a pointer that was passed to realloc printf("%d%d\n", *p, *q }

可能的输出:

12

无限循环无副作用

选择clang以观察显示的输出。

#include <stdio.h> int fermat() { const int MAX = 1000; int a=1,b=1,c=1; // Endless loop with no side effects is 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(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.

参考

  • C11标准(ISO / IEC 9899:2011):