4

次のコードを検討してください。

class cFoo {
    private:
        int m1;
        char m2;
    public:
        int doSomething1();
        int doSomething2();
        int doSomething3();
}

class cBar {
    private:
        cFoo mFoo;
    public:
        cFoo getFoo(){ return mFoo; }
}

void some_function_in_the_callstack_hierarchy(cBar aBar) {
    int test1 = aBar.getFoo().doSomething1();
    int test2 = aBar.getFoo().doSomething2();
    ...
}

getFoo() が呼び出される行で、コンパイラは cFoo の一時オブジェクトを生成し、doSomething1() を呼び出せるようにします。コンパイラは、これらの一時オブジェクトに使用されるスタック メモリを再利用しますか? 「some_function_in_the_callstack_hierarchy」の呼び出しで確保されるスタック メモリの数は? 生成された一時ごとにメモリを予約しますか?

私の推測では、コンパイラは cFoo の 1 つのオブジェクトに対してのみメモリを予約し、別の呼び出しに対してメモリを再利用しますが、追加すると

    int test3 = aBar.getFoo().doSomething3();

「some_function_in_the_callstack_hierarchy」に必要なスタック サイズは、追加のローカル int 変数のためだけでなく、はるかに大きいことがわかります。

一方、私が次に交換する場合

cFoo getFoo(){ return mFoo; }

参照付き (プライベート メンバーへの参照を返すのは良くないため、テスト目的のみ)

const cFoo& getFoo(){ return mFoo; }

1 つの cFoo のサイズよりもはるかに少ないスタック メモリが必要です。

したがって、コンパイラは、関数内で生成されたすべての一時オブジェクトに対して追加のスタック メモリを予約しているようです。しかし、これは非常に非効率的です。誰かがこれを説明できますか?

4

3 に答える 3

5

最適化コンパイラは、ソース コードを何らかの内部表現に変換し、正規化しています。

フリー ソフトウェアコンパイラ ( GCCClang/LLVMなど) を使用すると、その内部表現を調べることができます (少なくとも、コンパイラ コードにパッチを適用するか、デバッガで実行することによって)。

ところで、一時的な値は、最適化されているか、レジスタに格納できるなどの理由で、スタック領域を必要としない場合があります。そして、現在の呼び出しフレームで不要なスロットを再利用することがよくあります。また、(特に C++ では) 多くの (小さい) 関数がインライン化されていますgetFoo(おそらくそうであるように) (そのため、コール フレーム自体がありません)。最近の GCC では、末尾呼び出しの最適化が可能な場合もあります(基本的に、呼び出し元の呼び出しフレームを再利用します)。

GCC(つまり)でコンパイルする場合は、最適化オプション開発者オプション(およびその他)を使用することをおg++勧めします。おそらく、 (または呼び出しフレームあたりのバイト単位の他の値)および/またはも使用します -Wstack-usage=48-fstack-usage

まず、アセンブラコードが読める場合は、コンパイルyourcode.ccg++ -S -fverbose-asm -O yourcode.ccて、出力されたyourcode.s

(最適化フラグで遊ぶことを忘れないでください。 or に置き換えて-Oください....)-O2-O3

次に、コンパイラがどのように最適化するかについてもっと知りたい場合は、試してみると、GCC に関連する内部表現の部分的なテキスト レンダリングg++ -O -fdump-tree-all -c yourcode.ccを含む、いわゆる「ダンプ ファイル」がたくさん得られます。

さらに興味がある場合は、私のGCC MELTと、特にそのドキュメントページ (多くのスライドと参照が含まれています) を調べてください。

したがって、コンパイラは、関数内で生成されたすべての一時オブジェクトに対して追加のスタック メモリを予約しているようです。

確かに、一般的なケースではそうではありません (もちろん、いくつかの最適化を有効にすると仮定します)。また、いくらかのスペースが予約されていても、すぐに再利用されます。

ところで: C++11 標準はスタックについて言及していないことに注意してください。スタックを使用せずにコンパイルされた C++ プログラムを想像することができます (たとえば、プログラム全体の最適化で、スタック空間とレイアウトを最適化してスタックを回避できる再帰のないプログラムを検出します。私はそのようなコンパイラを知りませんが、そのようなコンパイラは知っています)。かなり賢いかもしれません....)

于 2016-10-14T11:57:59.607 に答える
4

コンパイラが特定のコードをどのように処理するかを分析しようとする試みは、最適化戦略が積極的になるにつれて、ますます困難になっています。

コンパイラがしなければならないことは、C++ 標準を実装し、副作用を導入またはキャンセルせずにコードをコンパイルすることだけです (戻り値や名前付き戻り値の最適化などのいくつかの例外があります)。

コードからわかるように、cFooはポリモーフィック型ではなく、メンバー データがないため、コンパイラはオブジェクトの作成を完全に最適化し、本質的にstatic関数であるものを直接呼び出すことができます。私が書いている時点でも、一部のコンパイラはすでにそれを行っていると思います。出力アセンブリをいつでも確認できます。

編集:OPはクラスメンバーを導入しました。しかし、これらは決して初期化されprivateず、 であるため、コンパイラーはそれについてあまり考えずにそれらを削除できます。したがって、この答えは引き続き適用されます。

于 2016-10-14T11:59:04.513 に答える
1

一時オブジェクトの有効期間は、完全に含まれる式の終わりまでです。標準の「12.2 一時オブジェクト」の段落を参照してください。

最小の最適化設定を使用しても、一時オブジェクトの有効期間が終了した後にコンパイラがスペースを再利用しない可能性はほとんどありません。

于 2016-10-14T12:14:57.513 に答える