試してみました:
;# As you have already noticed, the compiler wants to align the stack
;# pointer on a 16 byte boundary before it pushes anything. That's
;# because certain instructions' memory access needs to be aligned
;# that way.
;# So in order to first save the original offset of esp (+4), it
;# executes the first instruction:
lea ecx,[esp+0x4]
;# Now alignment can happen. Without the previous insn the next one
;# would have made the original esp unrecoverable:
and esp,0xfffffff0
;# Next it pushes the return addresss and creates a stack frame. I
;# assume it now wants to make the stack look like a normal
;# subroutine call:
push DWORD PTR [ecx-0x4]
push ebp
mov ebp,esp
;# Remember that ecx is still the only value that can restore the
;# original esp. Since ecx may be garbled by any subroutine calls,
;# it has to save it somewhere:
push ecx
これは、スタックを 16 バイト境界に揃えるために行われます。一部の命令では、特定のデータ型を 16 バイト境界まで揃える必要があります。この要件を満たすために、GCC は、スタックが最初は 16 バイトにアラインされていることを確認し、スタック スペースを 16 バイトの倍数で割り当てます。これは、オプション -mpreferred-stack-boundary=num を使用して制御できます . -mpreferred-stack-boundary=2 (2=4 バイト アラインメントの場合) を使用する場合、スタックは常に少なくとも 4 バイト アラインされるため、このアラインメント コードは生成されません。ただし、より強力なアラインメントが必要なデータ型をプログラムで使用すると、問題が発生する可能性があります。
gcc マニュアルによると:
<ブロック引用>Pentium および PentiumPro では、double および long double の値は 8 バイト境界に揃える必要があります (-malign-double を参照)。そうしないと、実行時のパフォーマンスが大幅に低下します。 Pentium III では、ストリーミング SIMD 拡張命令 (SSE) のデータ型 __m128 は、16 バイトでアラインされていない場合、正しく機能しない可能性があります。
スタック上でこの値が適切に配置されるようにするには、スタック境界を、スタックに格納されている値が必要とするのと同じように配置する必要があります。さらに、すべての関数は、スタックの整列を維持するように生成する必要があります。したがって、優先度の高いスタック境界でコンパイルされた関数を、優先度の低いスタック境界でコンパイルされた関数から呼び出すと、スタックの位置合わせがずれてしまう可能性が高くなります。コールバックを使用するライブラリでは常にデフォルト設定を使用することをお勧めします。
この余分なアラインメントは余分なスタック スペースを消費し、一般的にコード サイズを増加させます。組み込みシステムやオペレーティング システム カーネルなど、スタック スペースの使用に敏感なコードでは、優先アライメントを -mpreferred-stack-boundary=2 に減らしたい場合があります。
lea
元のスタック ポインタをロードします (main
への呼び出しの前から) ) ecx
に 、スタック ポインターが変更されようとしているためです。これは 2 つの目的で使用されます:
main
への引数にアクセスするには 関数、元のスタック ポインターに相対的であるためmain
から戻るときにスタック ポインタを元の値に戻す lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ?
and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ???
push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ?
push ebp
mov ebp,esp
push ecx ;why is ecx pushed too ??
任意にアラインされたオペランドにもかかわらず、すべての命令が速度の低下なしに完全に機能したとしても、アラインメントによってパフォーマンスは向上します。 2 つのキャッシュ ラインと重なる 16 バイトの量を参照するループを想像してみてください。さて、その小さな wchar をキャッシュにロードするには、2 つのキャッシュ ライン全体を削除する必要があります。同じループでそれらが必要な場合はどうすればよいでしょうか?キャッシュは RAM よりも非常に高速であるため、キャッシュのパフォーマンスは常に重要です。
また、通常、ミスアラインされたオペランドをレジスタにシフトすると、速度が低下します。スタックが再アラインされていることを考えると、パラメーターのスタック フレームをトラバースして戻るために、当然、古いアラインメントを保存する必要があります。
ecx は一時レジスターなので、保存する必要があります。また、最適化レベルによっては、プログラムを実行するために厳密には必要ではないと思われるフレーム リンケージ ops の一部が、トレース対応フレーム チェーンを設定するために重要になる場合があります。