c++ - メソッド/関数を呼び出すと、アセンブリ言語ではどうなりますか?



assembly function (10)

私がC ++ / Cで(言語はそれほど重要ではなく、単に概念を説明するために)必要なプログラムを持っていれば:

#include <iostream>    

void foo() {
    printf("in foo");
}

int main() {
    foo();
    return 0;
}

アセンブリではどうなりますか? 私は実際にアセンブリコードを探しているわけではありません。なぜなら、まだそれほど遠くにあるわけではありませんが、基本原則は何ですか?


Answers

一般的には、次のようになります。

  1. 関数の引数はスタックに格納されます。 プラットフォーム固有の順序で。
  2. 戻り値の位置はスタックに "割り当てられ"ます
  3. 関数の戻りアドレスは、スタックまたは専用CPUレジスタにも格納されます。
  4. 関数(または実際には関数のアドレス)は、CPU固有のcall命令または通常のjmp命令またはbr命令(ジャンプ/分岐)によってcallます。
  5. この関数はスタックから引数を読み込み、関数コードを実行します
  6. 関数からの戻り値は、指定された場所(スタックまたは専用CPUレジスタ)に格納されます。
  7. 実行は呼び出し元にジャンプし、スタックはクリアされます(スタックポインタを初期値に戻すことによって)。

上記の詳細は、プラットフォームごとに異なります。コンパイラからコンパイラまでさまざまです(例えば、STDCALLとCDECL呼び出し規約を参照してください)。 たとえば、スタックにスタックを格納する代わりに、CPUレジスタが使用されることがあります。 しかし一般的なアイデアは同じです



何が起こるのですか? x86では、main関数の最初の行は次のようになります。

call foo

call命令は、スタック上の戻りアドレスをプッシュし、次にjmpをfooの位置にプッシュします。


一般的な考え方は、呼び出し元のメソッドで使用されるレジスタがスタックにプッシュされる(スタックポインタがESPレジスタにある)ことです。このプロセスは「レジスタをプッシュする」と呼ばれます。 時にはそれらもゼロになっていますが、それは依存しています。 アセンブリプログラマは、関数内でより多くの可能性を持たせるために、より多くのレジスタを解放し、共通の4( EAXEBXECX 、およびEDX on x86)を解放する傾向があります。

関数が終了すると、逆の場合も同じことが起こります。スタックは呼び出し前の状態に復元されます。 これは「レジスタをポップする」と呼ばれます。

更新:このプロセスは必ずしも実行する必要はありません。 コンパイラは、それを最適化して関数をインライン化することができます。

更新:通常、関数のパラメータはスタックの逆順にプッシュされ、スタックから取得されると通常の順序で表示されます。 この注文はCによって保証されていません(ref: Inner Loops by Rick Booth)


あなたはそれをあなた自身のために見ることができます:

Linuxでは、以下を使ってプログラムをコンパイルします。

gcc -S myprogram.c

そして、アセンブラでプログラムのリストを取得します(myprogram.s)。

もちろん、アセンブラについて理解するのは少し分かっているはずです(ただし、コンピュータの仕組みを理解するのに役立ちますので、学習する価値があります)。 関数を呼び出す(x86アーキテクチャでは)基本的には:

  • スタックに変数aを入れる
  • 変数bをスタックに入れる
  • 変数nをスタックに入れる
  • 関数のアドレスにジャンプする
  • スタックから変数をロードする
  • 機能を果たす
  • クリーンスタック
  • メインに戻る

何が起こるのですか?

Cはアセンブリで何が起こるかを模倣します...

それはマシンにとても近く、何が起こるかを理解することができます

void foo() {
    printf("in foo");

/*

db mystring 'in foo'
mov eax, dword ptr mystring
mov edx , dword ptr _printf
push eax
call edx
add esp, 8
ret
//thats it
*/

}

int main() {
    foo();
    return 0;
}

アセンブリではどうなりますか?

簡単な説明:現在のスタック状態が保存され、新しいスタックが作成され、実行される関数のコードがロードされて実行されます。 これは、あなたのマイクロプロセッサのいくつかのレジスタに迷惑をかけることと、狂ったやり方でメモリに読み書きすることと、いったん終了すると、呼び出し関数のスタック状態が復元されます。



1-呼び出しコンテキストがスタック上に確立される

2つのパラメータがスタックにプッシュされます

3-「コール」がメソッドに対して実行される


ここで本当の結果は何ですか?

プログラムがメモリをリークしました。 お使いのOSによっては、復旧された可能性があります。

ほとんどの現代のデスクトップオペレーティングシステム 、プロセスの終了時に漏れたメモリを回復しますが、他の多くの回答からも分かるように、この問題を無視するのは残念です。

しかし、あなたは頼りにすべきではない安全機能に頼っており、プログラム(または関数)は、この動作 次回は "ハード"なメモリリークを引き起こすシステム上で動作する可能性があります。

カーネルモードで動作している場合や、メモリ保護機能を使用していないヴィンテージ/組み込みオペレーティングシステムで動作している場合があります。 (MMUはダイスペースを占有し、メモリ保護はCPUサイクルを追加しますが、プログラマーが自分自身でクリーンアップすることはあまりありません)。

メモリは、好きなように使用して再利用できますが、終了する前にすべてのリソースの割り当てを解除してください。





c++ c assembly function