10

C++ では、ローカル変数は常にスタックに割り当てられます。スタックは、アプリケーションが占有できる許容メモリの一部です。そのメモリは RAM に保持されます (ディスクにスワップアウトされていない場合)。では、C++ コンパイラは常にローカル変数をスタックに格納するアセンブラ コードを作成するのでしょうか?

たとえば、次の単純なコードを見てください。

int foo( int n ) {
   return ++n;
}

MIPS アセンブラー コードでは、これは次のようになります。

foo:
addi $v0, $a0, 1
jr $ra

ご覧のとおり、n に対してスタックをまったく使用する必要はありませんでした。C++ コンパイラはそれを認識し、CPU のレジスタを直接使用しますか?

編集:うわー、あなたのほぼ即時かつ広範な回答に感謝します! return ++n;もちろん、 foo の関数本体はではなくであるべきですreturn n++;。:)

4

6 に答える 6

12

はい。「変数は常にスタックに配置する」というルールはありません。C++ 標準は、スタックについて何も述べていません。スタックが存在することも、レジスタが存在することも想定していません。コードの実装方法ではなく、コードの動作方法を示しているだけです。

コンパイラは、必要な場合にのみ変数をスタックに保存します。たとえば、変数が関数呼び出しを過ぎて存在しなければならない場合、または変数のアドレスを取得しようとする場合です。

コンパイラは愚かではありません。;)

于 2009-12-02T12:34:15.627 に答える
9

免責事項: MIPS についてはわかりませんが、x86 についてはある程度知っています。原理は同じであると思います。

通常の関数呼び出し規約では、コンパイラは の値をnスタックにプッシュして function に渡しますfoo。ただし、fastcall代わりにレジスタを介して値を渡すように gcc に指示するために使用できる規則があります。(MSVC にもこのオプションがありますが、その構文はわかりません。)

test.cpp:

int foo1 (int n) { return ++n; }
int foo2 (int n) __attribute__((fastcall));
int foo2 (int n) {
    return ++n;
}

上記を でコンパイルすると、次のようg++ -O3 -fomit-frame-pointer -c test.cppになりますfoo1

mov eax,DWORD PTR [esp+0x4]
add eax,0x1
ret

ご覧のとおり、スタックから値を読み込みます。

そして、ここにありますfoo2

lea eax,[ecx+0x1]
ret

これで、レジスタから直接値が取得されます。

もちろん、関数をインライン化すると、コンパイラは、指定した呼び出し規則に関係なく、より大きな関数の本体に単純な追加を行います。しかし、インライン化できない場合、これが発生します。

免責事項 2: 私は、コンパイラーを継続的に推測する必要があると言っているわけではありません。ほとんどの場合、これはおそらく実用的ではなく、必要でもありません。しかし、それが完璧なコードを生成するとは思わないでください。

編集 1:単純なローカル変数 (関数の引数ではない) について話している場合、はい、コンパイラはそれらをレジスタまたはスタックに割り当てます。

編集 2:呼び出し規約はアーキテクチャ固有のようで、Richard Pennington が回答で述べているように、MIPS は最初の 4 つの引数をスタックに渡します。したがって、あなたの場合、追加の属性を指定する必要はありません (実際には x86 固有の属性です)。

于 2009-12-02T12:34:29.363 に答える
8

はい、C/C++ を最適化すると最適化されます。さらに多くのこと:ここを参照してください: Felix von Leitners Compiler Survey .

とにかく、通常の C/C++ コンパイラはすべての変数をスタックに置くわけではありません。関数の問題はfoo()、変数がスタックを介して関数に渡される可能性があることです (システム (ハードウェア/OS) の ABI がそれを定義します)。

C のキーワードを使用すると、レジスターに変数を格納するのがおそらく適切であるというヒントregisterをコンパイラーに与えることができます。サンプル:

register int x = 10;

しかし、覚えておいてください: コンパイラはx、必要に応じて自由にレジスタに格納しません!

于 2009-12-02T12:34:20.027 に答える
6

答えはイエスです。これは、コンパイラ、最適化レベル、およびターゲット プロセッサによって異なります。

mips の場合、最初の 4 つのパラメータが小さい場合はレジスタに渡され、戻り値がレジスタに返されます。したがって、あなたの例では、スタックに何かを割り当てる必要はありません。

実際、真実はフィクションよりも奇なり。あなたの場合、パラメーターは変更されずに返されます。返される値は、++ 演算子の前の n の値です。

foo:
    .frame  $sp,0,$ra
    .mask   0x00000000,0
    .fmask  0x00000000,0

    addu    $2, $zero, $4
    jr      $ra
    nop
于 2009-12-02T12:35:46.823 に答える
2

サンプルfoo関数は恒等関数であるため (引数を返すだけです)、私の C++ コンパイラ (VS 2008) はこの関数呼び出しを完全に削除します。それを次のように変更すると:

int foo( int n ) {
   return ++n;
}

コンパイラはこれをインライン化します

lea edx, [eax+1] 
于 2009-12-02T12:45:30.990 に答える
0

はい、レジスタは C++ で使用されます。MDR (メモリ データ レジスタ) には、フェッチおよび格納されるデータが含まれます。たとえば、セル 123 の内容を取得するには、値 123 (バイナリ) を MAR にロードし、フェッチ操作を実行します。操作が完了すると、セル 123 の内容のコピーが MDR に作成されます。値 98 をセル 4 に格納するには、4 を MAR にロードし、98 を MDR にロードしてストアを実行します。操作が完了すると、セル 4 の内容は 98 に設定され、以前にあったものはすべて破棄されます。データおよびアドレスレジスタは、これを達成するためにそれらと連携します。C++ でも、var を値で初期化したり、その値を要求したりすると、同じ現象が起こります。

そして、もう1つ、最新のコンパイラはレジスタ割り当ても実行します。これは、メモリ割り当てよりも少し高速です。

于 2017-07-24T09:37:55.597 に答える