Zkoušel jsem to:
;# 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
To se provádí, aby byl zásobník zarovnán na hranici 16 bajtů. Některé instrukce vyžadují, aby určité datové typy byly zarovnány až na hranici 16 bajtů. Aby GCC splnil tento požadavek, zajišťuje, aby byl zásobník zpočátku zarovnán na 16 bajtů, a přiděluje prostor zásobníku v násobcích 16 bajtů. To lze ovládat pomocí volby -mpreferred-stack-boundary=num . Pokud použijete -mpreferred-stack-boundary=2 (pro 2=4bajtové zarovnání), tento kód zarovnání se nevygeneruje, protože zásobník je vždy zarovnán alespoň na 4bajty. Pokud však váš program používá jakékoli datové typy, které vyžadují silnější zarovnání, můžete mít potíže.
Podle příručky gcc:
Na systémech Pentium a PentiumPro by hodnoty double a long double měly být zarovnány s 8bajtovou hranicí (viz -malign-double), jinak by měly být výrazně sníženy výkony za běhu. Na Pentiu III nemusí datový typ Streaming SIMD Extension (SSE) __m128 fungovat správně, pokud není zarovnán na 16 bajtů.
Aby bylo zajištěno správné zarovnání těchto hodnot na zásobníku, musí být hranice zásobníku zarovnány tak, jak to vyžaduje jakákoli hodnota uložená v zásobníku. Dále musí být každá funkce generována tak, aby udržovala zásobník zarovnaný. Volání funkce zkompilované s vyšší preferovanou hranicí zásobníku z funkce zkompilované s nižší preferovanou hranicí zásobníku tedy s největší pravděpodobností nevyrovná zásobník. Je doporučeno, aby knihovny, které používají zpětná volání, vždy používaly výchozí nastavení.
Toto dodatečné zarovnání spotřebovává další místo v zásobníku a obecně zvyšuje velikost kódu. Kód, který je citlivý na využití prostoru zásobníku, jako jsou vestavěné systémy a jádra operačních systémů, může chtít snížit preferované zarovnání na -mpreferred-stack-boundary=2.
lea
načte původní ukazatel zásobníku (z doby před voláním main
) do ecx
, protože ukazatel zásobníku se brzy změní. Používá se ke dvěma účelům:
- pro přístup k argumentům
main
funkce, protože jsou relativní k původnímu ukazateli zásobníku - pro obnovení ukazatele zásobníku na původní hodnotu při návratu z
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 ??
I kdyby každá instrukce fungovala perfektně bez omezení rychlosti navzdory libovolně zarovnaným operandům, zarovnání by stále zvýšilo výkon. Představte si smyčku odkazující na 16bajtové množství, které právě překrývá dva řádky mezipaměti. Nyní, aby bylo možné načíst tento malý wchar do mezipaměti, musí být vymazány dvě celé řádky mezipaměti a co když je potřebujete ve stejné smyčce? Mezipaměť je tak ohromně rychlejší než RAM, že výkon mezipaměti je vždy kritický.
Obvykle také dochází k penalizaci rychlosti za posunutí nesprávně zarovnaných operandů do registrů. Vzhledem k tomu, že se zásobník přerovnává, musíme přirozeně staré zarovnání uložit, abychom mohli procházet snímky zásobníku pro parametry a návrat.
ecx je dočasný registr, takže musí být uložen. V závislosti na úrovni optimalizace mohou být některé operace propojení rámců, které se nezdají být nezbytně nutné ke spuštění programu, také důležité pro nastavení řetězce rámců připravených ke sledování.