私は、コンパイラーがマシンコードを生成する方法、より具体的にはGCCがスタックを処理する方法をより深く理解しようとしています。そうすることで、私は単純なCプログラムを作成し、それらをアセンブリにコンパイルして、結果を理解するために最善を尽くしてきました。簡単なプログラムとそれが生成する出力は次のとおりです。
asmtest.c
:
void main() {
char buffer[5];
}
asmtest.s
:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
leave
ret
私が困惑しているのは、24バイトがスタックに割り当てられている理由です。プロセッサがメモリをアドレス指定する方法のため、スタックは4刻みで割り当てる必要があることを知っていますが、この場合、スタックポインタは24バイトではなく8バイトだけ移動する必要があります。参考までに、17のバッファバイトは、40バイト移動するスタックポインタを生成し、バッファなしでスタックポインタ8を移動します。1〜16バイトのバッファは、ESP
24バイトを移動します。
ここで、8バイトが必要な定数であると仮定すると(何のために必要ですか?)、これは、16バイトのチャンクで割り当てていることを意味します。なぜコンパイラはそのように整列するのでしょうか? x86_64プロセッサを使用していますが、64ビットのWordでも8バイトのアライメントしか必要ありません。なぜ不一致?
参考までに、gcc 4.0.1を使用して最適化を有効にしない10.5を実行しているMacでこれをコンパイルしています。
これは-mpreferred-stack-boundary=n
によって制御されるgcc機能であり、コンパイラーはスタック上のアイテムを2^n
に揃えようとします。 n
を2
に変更した場合、スタックには8バイトしか割り当てられません。 n
のデフォルト値は4
です。つまり、16バイトの境界に揃えようとします。
「デフォルト」の8バイトがあり、次に24 = 8 + 16バイトがあるのは、スタックにleave
とret
の8バイトがすでに含まれているため、コンパイルされたコードは最初にスタックを8ずつ調整する必要があるためです。 2 ^ 4 = 16に揃えるためのバイト数。
SSExファミリの命令では、パックされた128ビットベクトルを16バイトにアラインする必要があります。そうしないと、セグメンテーション違反が発生してロード/ストアしようとします。つまりスタック上のSSE)で使用するために16バイトのベクトルを安全に渡す場合、スタックは常に16に整列されている必要があります。GCCはデフォルトでそれを考慮します。
このサイト を見つけました。これには、スタックが大きくなる理由について、ページの下部に適切な説明があります。コンセプトを64ビットマシンにスケールアップすると、表示されている内容が説明される場合があります。
LWNにはメモリアライメントに関する記事があります 、おもしろいと思うかもしれません。
Mac OS X/Darwin x86 ABIには、16バイトのスタックアライメントが必要です。これは、Linux、Win32、FreeBSDなどの他のx86プラットフォームには当てはまりません...