2017-09-29 21 views
6

C++の関数から値を返す場面は何ですか?関数の戻り値の舞台裏++

いつもfunc。 (ローカル変数、func。引数とレジスタの敬称された順序)がコールスタックにプッシュされます。

しかし、出会いを実行してstatemenetを返すとどうなりますか?例えば

int a(int b){ 
    int c = b * 2; 
    return c; 
} 

return文に遭遇した後、EAXレジスタに格納されたCの値は、ローカル変数が破壊され、スタックフレームを呼び出しスタックから削除され、EAXレジスタの値が「返すアドレス」に移動されていることの後には、メモリ?

また、私はこの概念を誤解しましたか?

すべてのサポートは高く評価されています。ありがとう。

+0

呼び出し関数はEAX(またはどこでも)を使用します – Caleth

+5

最適化されていないケースで何が起こるか知りたいですか?十分なコンパイラが 'int foo = a(2);'を 'foo'を' 4 'で直接初期化できるので、私は尋ねます。 – NathanOliver

+2

コンパイラに関数のアセンブリ言語を出力するように指示します。 –

答えて

4

ところで、アセンブリ言語はプロセッサによって異なります。 ARMプロセッサにはEAXレジスタがありません。

コンパイラには、パラメータを渡してパラメータを返すための標準がある場合があります。関数から値を返すメソッドは、実装(コンパイラ)によって異なります。 すべてのコンパイラには標準がありません。

最適化されていないコード
コンパイラは、プロセッサ・レジスタを利用するように設計されています。

戻り値は、単一のレジスタに収まる場合には、レジスタの値を返すために使用されます。プロセッサーによって異なります。大きなオブジェクト/値について

、コンパイラは2つのオプションがあり:複数のレジスタオブジェクトを返すまたは値へのポインタを返します。ポインタは、スタックへのインデックスや値が格納されているアドレスなどの単純なものにすることができます。

最適化されたコード
コンパイラは、単純なプロセッサ命令を使用して機能を置き換えたり、コードをドロップすることがあります。この場合、戻り値はありません。

コンパイラは関数を定数に評価し、その定数を実行可能コードに入れることができます。関数呼び出しや関数の復帰が不要です。

コンパイラがインラインあなたの関数に決めることができます。この場合、代入文と同様の戻り値はありません。一時変数を使用して、値または別のレジスタを格納することができます。

詳細情報
詳細については、「コンパイラ理論」を参照してください。 ...

+0

プログラミングライフの中で、「Here be Dragons」という唯一の時間は、おそらく良いことです。 – user4581301

2

C++は、コンピュータメモリの理論モデルに対する操作の面で指定され、その上に龍との素敵な本があります。

また、「まるで」のルールとして知られている機能があります。これは、全体的な観察可能な効果が、あなたが書いたコードが文字通りメモリモデルに対する操作に変換されているかのようなものであれば、コンパイラが好きなコードを生成できることを意味します。

a(int):         # @a(int) 
     push rbp      
     mov  rbp, rsp     
     mov  dword ptr [rbp - 4], edi 
     mov  edi, dword ptr [rbp - 4] 
     shl  edi, 1 
     mov  dword ptr [rbp - 8], edi 
     mov  eax, dword ptr [rbp - 8] 
     pop  rbp 
     ret 

と、次の呼び出しのコードを与えられた:最適化されていないコードでは

は、生成アセンブラは、例えばgccがあなたの関数のために以下のコードを生成可能性があるため、コードで表現操作に非常に近い現実である

extern void foo(int x); 

int main() 
{ 
    foo(a(2)); 
} 

次のコードが生成されることがあります。

main:         # @main 
     push rbp 
     mov  rbp, rsp 
     mov  edi, 2 
     call a(int) 
     mov  edi, eax 
     call foo(int) 
     xor  eax, eax 
     pop  rbp 
     ret 

この単純なプログラムでは、コードの観察可能な効果は、値が4の引数でfooが呼び出されることです。aの呼び出しには、観察可能な副作用が1つしかありません。つまり、戻り値はその入力値の2倍です。

戻り値はfooに直接渡され、どこにも格納されないため、fooを呼び出してaを呼び出すことによるすべての副作用が完全に消費されたと言えるでしょう。

したがって、コンパイラがaの処理内容を知っている場合、それを呼び出すコードを生成する必要はありません。 fooを呼び出すことができます。値は 'like as'の場合​​はa(2)です。

実際に、最適化を追加することは私たちにこれを与える:

main:         # @main 
     push rax 
     mov  edi, 4   # note: 'as if' a(2) 
     call foo(int) 
     xor  eax, eax 
     pop  rcx 
     ret 

aの実装、この場合、(gccの上で)、次のとおりです。

a(int):         # @a(int) 
# 'as if' we created a variable and did some arithmetic, 
# stored the result and then returned the result 
     lea  eax, [rdi + rdi] 
     ret 
1

あなたは好奇心の用語に求めている場合は、 @トーマスマシューズの答えは非常に良いです...

特定のシナリオに関して質問がある場合は、自分で確認して結果を見て、アセンブリコードを読むのは難しいですが、確かに満足できるものです。それはアセンブリの多くを追加して分解する際に、printfを回避してみてください:あなたが見ることができるように、これはそれを取得するのと同じくらい簡単です

int func(int a, int b) 
{ 
    return a + b; 
} 

int main(int argc, char ** argv) 
{ 
    int a, b; 
    a = b = 100; 

    int c = func(a, b); 
} 

(ヒント:

は例えば、私は、GCCを使用して、次の例をコンパイルコード)。 -ggdb

コンパイルはそれがgdbと連携し、gdb <application>を使用して実行することが容易になりますように、そしてちょうど、あなたのメソッド内にブレークポイントを追加し、それがヒットし、コマンドdisassembleを実行するのを待ちます。出力は次のようになります:あなたが見ることができるように

Breakpoint 1, func (a=100, b=100) at program.cpp:3 
3   return a + b; 
(gdb) disas 
Dump of assembler code for function func(int, int): 
    0x00000000004004d6 <+0>:  push %rbp 
    0x00000000004004d7 <+1>:  mov %rsp,%rbp 
    0x00000000004004da <+4>:  mov %edi,-0x4(%rbp) 
    0x00000000004004dd <+7>:  mov %esi,-0x8(%rbp) 
=> 0x00000000004004e0 <+10>: mov -0x4(%rbp),%edx 
    0x00000000004004e3 <+13>: mov -0x8(%rbp),%eax 
    0x00000000004004e6 <+16>: add %edx,%eax 
    0x00000000004004e8 <+18>: pop %rbp 
    0x00000000004004e9 <+19>: retq 
End of assembler dump. 

は、ここでは、コンパイラが行う唯一のことは、RBP(pop %rbp)に、古いベースポインタをポップして、バック返品アドレス(retq)にジャンプしています。結果はすでにレジスタに格納されているので、何もする必要はありません。

関連する問題