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):