Undefined behavior
C语言标准精确地规定了C语言程序的可观察行为,除了以下几类:
- 未定义的行为 - 对程序的行为没有限制。未定义行为的例子是数组边界之外的内存访问,有符号整数溢出,空指针取消引用,在没有序列点的表达式中多次修改相同标量,通过不同类型的指针访问对象等。编译器不需要诊断未定义的行为(虽然诊断了许多简单情况),编译后的程序不需要做任何有意义的事情。
- 未指定的行为 - 允许两个或更多行为,并且不需要实现来记录每个行为的影响。例如,评估顺序,相同的字符串文字是否不同等。每个未指定的行为都会导致一组有效结果中的一个,并且在同一程序中重复时可能会产生不同的结果。
- 实现定义的行为 - 未指定的行为,其中每个实现记录如何进行选择。例如,一个字节中的位数,或者有符号整数右移是算术还是逻辑。
- 特定于语言环境的行为 - 实现定义的行为取决于当前选择的语言环境。例如,对于
islower
除26个小写拉丁字母以外的任何字符,是否返回true。
(注意:严格符合的程序不依赖于任何未指定的,未定义的或实现定义的行为)。
编译器需要为任何违反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):
- 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术语定义
本文档系腾讯云开发者社区成员共同维护,如有问题请联系 cloudcommunity@tencent.com