mer*_*ian 4 c++ undefined-behavior
C++ 不定义某些行为(例如更好的错误检查)的原因是什么?为什么不抛出一些错误并停止呢?
一些伪代码例如:
if (p == NULL && op == deref){
return "Invalid operation"
}
Run Code Online (Sandbox Code Playgroud)
对于整数溢出:
if(size > capacity){
return "Overflow"
}
Run Code Online (Sandbox Code Playgroud)
我知道这些都是非常简单的例子。但我很确定大多数 UB 都能被编译器捕获。那么为什么不实施它们呢?因为它确实很耗时并且不进行错误检查更快?一些 UB 可以通过单个 if 语句捕获。那么也许速度并不是唯一的问题?
因为每次使用指针时编译器都必须添加这些指令。C++ 程序使用大量指针。所以会有很多这样的指令供计算机运行。C++ 的哲学是您不应该为不需要的功能付费。如果需要空指针检查,可以编写空指针检查。制作一个自定义指针类来检查它是否为空非常容易。
在大多数操作系统上,取消引用空指针肯定会导致程序崩溃。整数溢出一定会回绕。C++ 不仅可以在这些操作系统上运行 - 它也可以在其他不支持的操作系统上运行。因此,该行为不是在 C++ 中定义的,而是在操作系统中定义的。
尽管如此,一些编译器编写者意识到,通过再次取消定义,他们可以使程序更快。因此,可以进行数量惊人的优化。有时会有一个命令行选项告诉编译器不要取消定义行为,例如-fwrapv.
常见的优化是简单地假设程序永远不会到达具有 UB 的部分。就在昨天,我看到一个问题,有人写了一个显然不是无限循环的:
int array[N];
// ...
for(int i = 0; i < N+1; i++) {
fprintf(file, "%d ", array[i]);
}
Run Code Online (Sandbox Code Playgroud)
但它是无限的。为什么?问问题的人已经打开了优化。编译器可以看到最后一次迭代中有 UB,因为array[N]超出了范围。所以它假设程序在最后一次迭代之前神奇地停止了,所以它不需要检查最后一次迭代并且可以删除该部分i < N+1。
大多数时候,由于宏或内联函数,这是有意义的。有人写了这样的代码:
int getFoo(struct X *px) {return (px == NULL ? -1 : px->foo);}
int blah(struct X *px) {
bar(px->f1);
printf("%s", px->name);
frobnicate(&px->theFrob);
count += getFoo(px);
}
Run Code Online (Sandbox Code Playgroud)
并且编译器可以做出非常合理的假设,即px不为空,因此它删除了px == NULL检查并getFoo(px)与px->foo. 这通常就是为什么编译器编写者选择保持它未定义,即使它可以很容易地定义。
编译器已经有开关来启用这些检查(这些检查称为消毒剂,例如-fsanitize=address,-fsanitize=undefined在 GCC 和 Clang 中)。
标准总是要求这些检查是没有意义的,因为它们会损害性能,因此您可能不希望在发布版本中使用它们。