当程序无论接下来发生什么都会导致未定义的行为时,未定义的行为就会发生。但是,您给出了以下示例。
int num = ReadNumberFromConsole();
if (num == 3) {
PrintToConsole(num);
*((char*)NULL) = 0; //undefined behavior
}
除非编译器知道 的定义PrintToConsole
,否则它不能删除if (num == 3)
条件。假设您有LongAndCamelCaseStdio.h
带有以下声明的系统标头PrintToConsole
。
void PrintToConsole(int);
没什么帮助,好吧。现在,让我们通过检查这个函数的实际定义来看看供应商是多么邪恶(或者可能不是那么邪恶,未定义的行为可能会更糟)。
int printf(const char *, ...);
void exit(int);
void PrintToConsole(int num) {
printf("%d\n", num);
exit(0);
}
编译器实际上必须假设编译器不知道它做什么的任何任意函数都可能退出或抛出异常(在 C++ 的情况下)。您会注意到它不会被执行,因为调用*((char*)NULL) = 0;
后执行不会继续。PrintToConsole
未定义的行为在PrintToConsole
实际返回时发生。编译器希望这不会发生(因为无论如何这都会导致程序执行未定义的行为),因此任何事情都可能发生。
但是,让我们考虑其他事情。假设我们正在做空检查,并在空检查后使用变量。
int putchar(int);
const char *warning;
void lol_null_check(const char *pointer) {
if (!pointer) {
warning = "pointer is null";
}
putchar(*pointer);
}
在这种情况下,很容易注意到它lol_null_check
需要一个非 NULL 指针。分配给全局非易失性warning
变量不会退出程序或引发任何异常。pointer
也是非易失性的,所以它不能在函数中间神奇地改变它的值(如果是,它是未定义的行为)。调用lol_null_check(NULL)
将导致未定义的行为,这可能导致变量未被分配(因为此时,程序执行未定义行为的事实是已知的)。
然而,未定义的行为意味着程序可以做任何事情。因此,没有什么可以阻止未定义的行为回到过去,并在第一行int main()
执行之前使程序崩溃。这是未定义的行为,它不必有意义。它也可能在输入 3 后崩溃,但未定义的行为会及时返回,甚至在您输入 3 之前崩溃。谁知道呢,也许未定义的行为会覆盖您的系统 RAM,并导致您的系统在 2 周后崩溃,当您的未定义程序未运行时。