为什么增强的GCC 6优化器打破实用的C ++代码? [英] Why does the enhanced GCC 6 optimizer break practical C++ code?

查看:167
本文介绍了为什么增强的GCC 6优化器打破实用的C ++代码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

GCC 6有一个新的优化器功能:它假设始终不为null,并根据此进行优化。


值范围传播现在假定C ++成员的this指针函数是非空的。这消除了常见的空指针检查,但也打破了一些不合格的代码库(例如Qt-5,Chromium,KDevelop)。作为一个临时工作环境,可以使用-fno-delete-null-pointer-checks。错误的代码可以通过使用-fsanitize = undefined标识。


更改文档清楚地将其称为危险的,因为它打破了惊人的数量

为什么这个新的假设破坏了实用的C ++代码?有粗心或不知情的程序员依赖这个特定的未定义的行为?我不能想象任何人写 if(this == NULL),因为那是不自然的。

解决方案

我想要的问题,需要回答为什么善意的人会写的支票在第一。



最常见的情况是如果你有一个类是自然发生的递归调用的一部分。



如果你有:

  struct Node 
{
节点*左;
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(){
//< ---这里应该检查什么?
left-> traverse_in_order();
process();
right-> traverse_in_order();
}

在C ++的早期(标准化之前)成员函数是函数的语法糖,其中this参数是隐式的。代码用C ++编写,转换为等效的C并编译。甚至有明显的例子,比较这与null是有意义的,原来的Cfront编译器利用了这一点。所以来自C的背景,检查的明显选择是:

  if(this == nullptr)return; 

注意:Bjarne Stroustrup甚至提到 this 已更改多年这里



这在许多编译器工作多年。当标准化发生时,这改变了。最近,编译器开始利用调用一个成员函数,其中 this nullptr 是未定义的行为,这个条件总是 false ,编译器可以省略它。



这意味着要做任何遍历您需要:




  • 在调用traverse_in_order之前执行所有检查

      void Node :: traverse_in_order(){
    if(left)left-> traverse_in_order
    process();
    if(right)right-> traverse_in_order();
    }

    这意味着,如果你可以有一个空根,也检查每个调用站点。

    风格代码(也许作为一个静态方法),并显式地作为参数调用它的对象。例如。你回到写 Node :: traverse_in_order(node); 而不是 node-> traverse_in_order();


  • 我认为以符合标准的方式修复这个特定示例的最简单/最简单的方法是实际使用一个哨兵节点

      //静态类或全局变量
    节点sentinel;

    void Node :: traverse_in_order(){
    if(this ==& sentinel)return;
    ...
    }




前两个选项似乎都不吸引人,虽然代码可能会失去它,他们用 this == nullptr 写了错误的代码,而不是使用适当的修复。



我猜想这些代码库是如何演变成 this == nullptr



编辑:添加了一个正确的解决方案,因为人们似乎想为这个响应提供一个例子,为什么人们可能有写nullptr检查。

编辑5月9日:添加链接到Bjarne Stroustroup对中的更改的评论


GCC 6 has a new optimizer feature: It assumes that this is always not null and optimizes based on that.

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.

The change document clearly calls this out as dangerous because it breaks a surprising amount of frequently used code.

Why would this new assumption break practical C++ code? Are there particular patterns where careless or uninformed programmers rely on this particular undefined behavior? I cannot imagine anyone writing if (this == NULL) because that is so unnatural.

解决方案

I guess the question that needs to be answered why well-intentioned people would write the checks in the first place.

The most common case is probably if you have a class that is part of a naturally occurring recursive call.

If you had:

struct Node
{
  Node* left;
  Node* right;
};

in C, you might write:

void traverse_in_order(Node* n) {
    if(!n) return;
    traverse_in_order(n->left);
    process(n);
    traverse_in_order(n->right);
}

In C++, it's nice to make this a member function:

void Node::traverse_in_order() {
    // <--- What check should be put here?
    left->traverse_in_order();
    process();
    right->traverse_in_order();
}

In the early days of C++ (prior to standardization), it was emphasized that that member functions were syntactic sugar for a function where the this parameter is implicit. Code was written in C++, converted to equivalent C and compiled. There were even explicit examples that comparing this to null was meaningful and the original Cfront compiler took advantage of this too. So coming from a C background, the obvious choice for the check is:

if(this == nullptr) return;      

Note: Bjarne Stroustrup even mentions that the rules for this have changed over the years here

And this worked on many compilers for many years. When standardization happened, this changed. And more recently, compilers started taking advantage of calling a member function where this being nullptr is undefined behavior, which means that this condition is always false, and the compiler is free to omit it.

That means that to do any traversal of this tree, you need to either:

  • Do all of the checks before calling traverse_in_order

    void Node::traverse_in_order() {
        if(left) left->traverse_in_order();
        process();
        if(right) right->traverse_in_order();
    }
    

    This means also checking at EVERY call site if you could have a null root.

  • Don't use a member function

    This means that you're writing the old C style code (perhaps as a static method), and calling it with the object explicitly as a parameter. eg. you're back to writing Node::traverse_in_order(node); rather than node->traverse_in_order(); at the call site.

  • I believe the easiest/neatest way to fix this particular example in a way that is standards compliant is to actually use a sentinel node rather than a nullptr.

    // static class, or global variable
    Node sentinel;
    
    void Node::traverse_in_order() {
        if(this == &sentinel) return;
        ...
    }
    

Neither of the first two options seem that appealing, and while code could get away with it, they wrote bad code with this == nullptr instead of using a proper fix.

I'm guessing that's how some of these code bases evolved to have this == nullptr checks in them.

edit: Added a proper solution, since people seem to want to provide one for this response that was just written as an example as to why people might have written nullptr checks.

Edit May 9: Added link to Bjarne Stroustroup's comments on the change in this

这篇关于为什么增强的GCC 6优化器打破实用的C ++代码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆