2

C でのプログラムの実行について読むときはいつでも、関数の実行についてほとんど話していません。プログラムが別の関数から呼び出されてから戻るまでに実行を開始したときに関数に何が起こるかをまだ調べようとしていますか? 関数の引数はどのようにメモリに格納されますか?

4

2 に答える 2

9

それは特定されていません。それは実装次第です。キース・トンプソンが指摘したように、それがどのように機能するかをあなたに伝える必要さえありません。:)

いくつかの実装はすべての引数をスタックに置き、いくつかはレジスターを使用し、多くはミックスを使用します(最初のn個の引数はレジスターに渡され、それ以上はスタックに入れられます)。

ただし、関数自体は単なるコードであり、読み取り専用であり、実行中に「発生」することはほとんどありません。

于 2012-08-31T08:16:28.007 に答える
3

この質問に対する正しい答えは1つではありません。これは、プログラマーの作成者がこれを行うのに最適なモデルであると判断する方法に大きく依存します。このプロセスを説明する標準にはさまざまなビットがありますが、そのほとんどは実装定義です。また、プロセスは、システムのアーキテクチャ、目的のOS、最適化のレベルなどによって異なります。

次のコードを取ります:-

int DoProduct (int a, int b, int c)
{
  return a * b * c;
}

int result = DoProduct (4, 5, 6);

MSVC2005コンパイラは、標準のデバッグビルドオプションを使用して、上記のコードの最後の行にこれを作成しました。-

push        6    
push        5    
push        4    
call        DoProduct (411186h) 
add         esp,0Ch 
mov         dword ptr [ebp-18h],eax 

ここで、引数はスタックにプッシュされ、最後の引数から始まり、最後から2番目の引数が続き、最初の引数がスタックにプッシュされます。関数が呼び出され、引数がスタック(add esp,0ch)から削除され、戻り値が保存されます。結果はeaxレジスタに格納されます。

関数のコードは次のとおりです。-

push        ebp  
mov         ebp,esp 
sub         esp,0C0h 
push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0C0h] 
mov         ecx,30h 
mov         eax,0CCCCCCCCh 
rep stos    dword ptr es:[edi] 
mov         eax,dword ptr [a] 
imul        eax,dword ptr [b] 
imul        eax,dword ptr [c] 
pop         edi  
pop         esi  
pop         ebx  
mov         esp,ebp 
pop         ebp  
ret              

関数が最初に行うことは、ローカルスタックフレームを作成することです。これには、ローカル変数と一時変数を格納するためのスペースをスタックに作成することが含まれます。この場合、192(0xc0)バイトが予約されています(最初の3つの命令)。それが非常に多い理由は、編集と続行機能に新しい変数を入れるためのスペースを許可するためです。

次の3つの命令は、MSコンパイラによって定義された予約済みレジスタを保存します。次に、作成されたスタックフレームスペースが初期化され、特別なデバッグ署名(この場合は0xCC)が含まれます。つまりunitialised memory、デバッグモードで0xCCだけで構成される値が表示された場合は、値を初期化するのを忘れています(0xCCが値でない限り)。

すべてのハウスキーピングが完了すると、次の3つの命令は、関数の本体である2つの乗算を実装します。その後、予約されたレジスタが復元され、スタックフレームが破棄され、最後に関数が。で終了しますret。幸い、imulは乗算の結果をeaxレジスタに入れるので、結果を正しいレジスタに入れるための特別なコードはありません。

さて、あなたはおそらく、本当に必要ではないことがたくさんあると思っていたでしょう。そして、あなたは正しいですが、デバッグはコードを正しくすることであり、上記の多くはそれを達成するのに役立ちます。リリースでは、取り除くことができることがたくさんあります。スタックフレームは必要ないため、初期化する必要はありません。予約済みのレジスタは変更されないため、保存する必要はありません。実際、コンパイラはこれを作成します:-

mov         eax,dword ptr [esp+4] 
imul        eax,dword ptr [esp+8] 
imul        eax,dword ptr [esp+0Ch] 
ret              

これをコンパイラーに任せれば、呼び出し元にインライン化されます。

発生する可能性のあるものは他にもたくさんあります。レジスタに渡される値などです。また、関数との間で浮動小数点値と構造体/クラスがどのように受け渡されるかについても理解していません。そして、私がおそらく省略したものはまだまだあります。

于 2012-08-31T08:39:17.700 に答える