強化された GCC 6 オプティマイザーが実用的な C++ コードを破壊するのはなぜですか? 質問する

強化された GCC 6 オプティマイザーが実用的な C++ コードを破壊するのはなぜですか? 質問する

GCC 6には新しいオプティマイザ機能があります: はthis常に null ではないと想定し、それに基づいて最適化します。

値の範囲の伝播では、C++メンバー関数のthisポインタが非nullであると想定されるようになりました。これにより、一般的なnullポインタのチェックが不要になります。しかし、Qt-5、Chromium、KDevelopなどの非準拠コードベースも壊れます。一時的な回避策として、-fno-delete-null-pointer-checks を使用できます。-fsanitize=undefined を使用すると、間違ったコードを識別できます。

変更ドキュメントでは、頻繁に使用されるコードが驚くほど多く壊れるため、これは危険であると明確に指摘されています。

この新しい仮定によって実用的な C++ コードが壊れるのはなぜでしょうか?if (this == NULL)不注意なプログラマーや知識のないプログラマーがこの特定の未定義の動作に頼る特定のパターンはありますか?これは非常に不自然なので、誰かが書くとは想像できません。

ベストアンサー1

答えが必要な疑問は、そもそもなぜ善意の人々が小切手を切るのかということだと思います。

最も一般的なケースは、自然に発生する再帰呼び出しの一部となるクラスがある場合です。

もしあなたが持っていたなら:

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 に変換されてコンパイルされました。null との比較が意味のある明示的な例さえありthis、オリジナルの 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();
    }
    

    これは、すべての呼び出しサイトで null ルートがある可能性があるかどうかもチェックすることを意味します。

  • メンバー関数を使用しない

    これは、古い 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;
        ...
    }
    

最初の 2 つのオプションはどちらもそれほど魅力的ではないようです。コードでは問題なく実行できたものの、this == nullptr適切な修正を使用せずに不適切なコードが書かれてしまいました。

一部のコード ベースがthis == nullptrチェック機能を備えるようになったのは、このような理由からだと思います。

おすすめ記事