これは、ステートメントの逆アセンブルによって私が見るものですfunction(1,2,3);:
movl   $0x3,0x8(%esp)
movl   $0x2,0x4(%esp)
movl   $0x1,(%esp)
call   0x4012d0 <_Z8functioniii>
ret アドレスがまったくスタックにプッシュされていないようですが、どのように機能しますretか?
これは、ステートメントの逆アセンブルによって私が見るものですfunction(1,2,3);:
movl   $0x3,0x8(%esp)
movl   $0x2,0x4(%esp)
movl   $0x1,(%esp)
call   0x4012d0 <_Z8functioniii>
ret アドレスがまったくスタックにプッシュされていないようですが、どのように機能しますretか?
x86 プロセッサでは (アセンブリ言語の例のように)、call命令はリターン アドレスをスタックにプッシュし、制御を関数に移します。
そのため、関数へのエントリでは、スタック ポインターは戻りアドレスを指してretおり、プログラム カウンター (EIP / RIP) にポップする準備ができています。
すべてのプロセッサ アーキテクチャがリターン アドレスをスタックに置くわけではありません。多くの場合、リターン アドレスを保持するように設計された 1 つまたは複数のレジスタのセットがあります。ARM プロセッサでは、BL命令は戻りアドレスを特定のレジスタ (LRまたは「リンク レジスタ」) に配置し、制御を関数に渡します。
ia64 プロセッサも同様のことを行いますが、戻りアドレスを受け取ることができるレジスタ ( b0- ) がいくつかあり、そのうちの 1 つが命令で指定されます (これがデフォルトです)。b7b0
理想的には、callステートメントはそれを処理する必要があります。プログラム カウンターの次の位置がスタックにプッシュされます。呼び出された関数 (サブルーチン) が動作を完了し、return ステートメントに遭遇すると、コントロールはスタックにプッシュされたアドレスに移動し、ポップされます。
callこれは ABI とアーキテクチャに依存しますが、リターン アドレスがスタックに置かれる場合は、そこに置く命令の副作用です。
callは RIP レジスタの現在の値 (戻りアドレス) をスタックにプッシュします + 呼び出し
retは戻りアドレス (プッシュされた呼び出し) をスタックの一番上からポップし (RSP レジスタはそこを指します)、RIP レジスタに書き込みます。
GNU/Linux ボックスでの例: 関数 f は関数 g を呼び出し、g のフレームを見てみましょう。
ローアドレス
... <- RSP (スタック ポインタはスタックのトップを示します) レジスタはこのアドレスを指します
g のローカル変数
f のベース ポインタ (古い RBP 値) <- RBP (ベース ポインタ) レジスタはこのアドレスを指します
f の ret アドレス (古い RIP 値) (これは(fからの)呼び出しがプッシュしたものであり、(gからの) retがポップするものです)
fがgを呼び出してレジスタに収まらなかった引数(Windowsではこれは異なると思います)
...  
ハイアドレス
g はローカル変数を解放します (movq %rsp, %rbp) 
g は「古い RBP」をポップし、RBP レジスターに格納します (pop %rbp) 
g retは、RSP が指す場所に格納されている値で RIP を変更しますで
それが役に立てば幸い