GCC 6 has a new optimizer feature : 它假定 this
始终不为空,并以此为基础进行优化。
Value range propagation now assumes that the this pointer of C++ member functions is non-null. This eliminates common null pointer checks but also breaks some non-conforming code-bases (such as Qt-5, Chromium, KDevelop). As a temporary work-around -fno-delete-null-pointer-checks can be used. Wrong code can be identified by using -fsanitize=undefined.
更改文档明确指出这是危险的,因为它破坏了数量惊人的常用代码。
为什么这个新假设会破坏实际的 C++ 代码? 粗心或不知情的程序员是否会依赖这种特定的未定义行为?我无法想象有人会写 if (this == NULL)
,因为那太不自然了。
最佳答案
我想需要回答的问题是为什么好心的人会首先写支票。
最常见的情况可能是您的类是自然发生的递归调用的一部分。
如果你有:
struct Node
{
Node* left;
Node* right;
};
在 C 中,你可以这样写:
void traverse_in_order(Node* n) {
if(!n) return;
traverse_in_order(n->left);
process(n);
traverse_in_order(n->right);
}
在 C++ 中,将其设为成员函数非常好:
void Node::traverse_in_order() {
// <--- What check should be put here?
left->traverse_in_order();
process();
right->traverse_in_order();
}
在 C++ 的早期(标准化之前),人们强调成员函数是函数的语法糖,其中 this
参数是隐式的。代码用 C++ 编写,转换为等效的 C 并编译。甚至有明确的例子表明将 this
与 null 进行比较是有意义的,并且最初的 Cfront 编译器也利用了这一点。因此,来自 C 背景,检查的明显选择是:
if(this == nullptr) return;
注意:Bjarne Stroustrup 甚至提到 this
的规则多年来已经发生了变化here
这在许多编译器上工作了很多年。当标准化发生时,情况发生了变化。最近,编译器开始利用调用成员函数的优势,其中 this
为 nullptr
是未定义的行为,这意味着此条件始终为 false
,编译器可以随意省略它。
这意味着要对这棵树进行任何遍历,您需要:
在调用
之前做所有的检查traverse_in_order
void Node::traverse_in_order() { if(left) left->traverse_in_order(); process(); if(right) right->traverse_in_order(); }
这意味着还要检查每个调用站点是否可以有一个空根。
不要使用成员函数
这意味着您正在编写旧的 C 样式代码(可能作为静态方法),并使用对象作为参数显式调用它。例如。你又回到了在调用站点编写
Node::traverse_in_order(node);
而不是node->traverse_in_order();
的地方。我相信以符合标准的方式修复此特定示例的最简单/最简洁的方法是实际使用哨兵节点而不是
nullptr
。// static class, or global variable Node sentinel; void Node::traverse_in_order() { if(this == &sentinel) return; ... }
前两个选项似乎都不吸引人,虽然代码可以摆脱它,但他们使用 this == nullptr
编写了糟糕的代码,而不是使用适当的修复程序。
我猜这就是其中一些代码库如何演变为在其中包含 this == nullptr
的检查。
关于c++ - 为什么增强的 GCC 6 优化器会破坏实用的 C++ 代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36893251/