2009-12-02 22 views
10

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

テイク、例えば、次の簡単なコード:

MIPSアセンブラコードで
int foo(int n) { 
    return ++n; 
} 

、これは次のようになります。

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

あなたが見ることができるように、私はする必要はありませんでしたnにはスタックをまったく使用しないでください。 C++コンパイラがそれを認識し、CPUのレジスタを直接使用するでしょうか?

編集:おかげさまで、あなたのほぼ即座で広範な回答に感謝します。 fooの関数本体は、ではなく、return ++n;でなければなりません。 :)

+0

コンパイラは最適化します。 x86 ISN - 'gccの-fverbose-ASM -O2 -S yoursource.c'その後、偉大なリンクのために1 –

答えて

9

免責事項:

通常の関数呼び出し規約で

、コンパイラはnの値をプッシュします..私はMIPSを知らないが、私はいくつかのx86を知っている、と私は、原理は同じであるべきだと思いますをスタックに渡して、関数fooに渡します。しかし、fastcallという規則があり、代わりにgccに値を渡すよう伝えることができます。 (MSVCにもこのオプションがありますが、その構文がわかりません。g++ -O3 -fomit-frame-pointer -c test.cppと上記のコンパイル)

test.cpp:

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

、私はfoo1のために得る:

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

あなたが見ることができるように、それはスタックから値を読み込みます。

そして、ここでfoo2です:

lea eax,[ecx+0x1] 
ret 

今では、レジスタから直接値をとります。

もちろん、関数をインライン化すると、コンパイラは指定した呼び出し規約にかかわらず、より大きな関数の本体に簡単な追加を行います。しかし、あなたがそれをインラインにすることができないとき、これは起こるでしょう。

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

編集1:あなたはプレーンなローカル変数(引数は機能していない)話をしている場合は、[はい、コンパイラは、それが適当と考えるようにレジスタやスタック上に割り当てられます。

編集2:リチャードペニントンが答えたように、呼び出し規約はアーキテクチャ固有のもので、MIPSは最初の4つの引数をスタックに渡します。したがって、あなたの場合、余分な属性(実際にはx86固有の属性です)を指定する必要はありません。

+1

-Oが、それはデバッグに干渉しないマシン上のスタックフレームの設定を無効にしてみてください「tはそれらの一つ、あなたは排除するために別々-fomit-フレームポインタを必要とする 『(アンワインドスタックフレームでのデバッグのために実際に有用である、すなわち)冗長』スタックフレームの設定を – matja

+0

ええ、私は完全にそれについて忘れてしまいました。私はそれを修正します。しかし、違いは残っています。 – int3

+0

リンク時間の最適化を行うコンパイラは、すべてのコールサイトを表示および修正できるため、コールをすべて高速コールに変換できることも認識しています。 –

12

はい。 「変数は常にスタックに割り当てられる」というルールはありません。 C++標準では、スタックについて何も言及していません。スタックが存在するとは仮定されません。コードがどのように振る舞うべきか、どのように実装すべきかはわかりません。

コンパイラは、変数をスタックに格納するのは、たとえば、関数呼び出しを超えて生きていなければならないときや、そのアドレスを取得しようとするときだけです。

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

8

はい、最適なC/C++が最適化されます。さらにMUCH続き:See here: Felix von Leitners Compiler Survey

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

Cのregisterキーワードを使用すると、コンパイラにヒントの変数をレジスタに格納することをお勧めします。サンプル:

register int x = 10; 

しかし、覚えている:それはしたい場合は、コンパイラがレジスタにxを保存しないように自由です!

+0

yoursource.s''内側を見て –

6

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

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

実際、真実はフィクションよりも見知らぬ人です。あなたのケースでは、パラメータはそのまま返されます。返された値が++演算子の前にn個のものである:

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

    addu $2, $zero, $4 
    jr  $ra 
    nop 
2

あなたの例foo関数が恒等関数であるので、(それはちょうどそれが引数の返し)、私のC++コンパイラは、( VS 2008)はこの関数呼び出しを完全に削除します。私はそれを変更した場合:

lea edx, [eax+1] 
+0

はい、再びMIPS例に: 静的int型のfoo(int型N){ リターンN ++。 } INT料(){ 戻りFOO(5)。 }が得られる: .globl料 2 .ALIGN の.text .ent料金 料: .frame $ SP、0、$ RA .mask 0x00000000,0 .fmask 0x00000000,0 ADDIU $ 2 、$ゼロ、5 JR $ RA NOP .SETマクロ 、 .END料 .size料を並べ替える。料.SET –

0

とコンパイラのインライン

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

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

そして、もう一つ、最近のコンパイラは、メモリの割り当てよりちょっと速いですレジスタ割り当てを行います。

関連する問題