Výuka assembleru
7. Procedury a funkce
V tomto díle si něco řekneme o procedurách a jejich volání. Procedury a funkce jsou samozřejmou součástí
každého programu. Dokonce i ten nejjednodušší prográmek, který v assembleru (nebo v nějakém vyšším
programovacím jazyce) napíšeme, bude mít aspoň jednu proceduru. Ano, WinMain je procedura jako každá
jiná. Od ostatních se liší pouze tím, že je volána operačním systémem jako vstupní bod programu.
Pozn.: Ve vyšších programovacích jazycích se obvykle mluví o procedurách, funkcích, rutinách
nebo i podprogramech. Protože tyto pojmy jsou do značné míry podobné, nebudou v tomto článku rozlišovány.
Bude-li řeč o proceduře nebo funkci, rozumí se tím stejný element programu.
Existuje několik důvodů, proč je dobré program dělit na procedury namísto "lineárního" kódu, který je celý pouze ve WinMain:
Pro volání procedury se používá instrukce call. Tato instrukce uloží do zásobníku adresu následující instrukce za instrukcí call a provede skok na adresu, která je parametrem instrukce call. Důvodem ukládání návratové adresy do zásobníku je nutnost znát adresu, kam má být proveden návrat z funkce. Instrukce pro návrat z funkce, ret (obdoba příkazu "return" z C nebo "Exit" z Pascalu), jednoduše vybere z vrcholu zásobníku návratovou adresu a provede skok na tuto adresu. Tento princip zajišťuje, že je možné funkci volat z libovolného místa (i z jiné funkce) a návrat bude vždy proveden na správné místo. Dokonce i volání procedury ze sebe samé (tzv. rekurze) díky tomuto principu funguje. Jediné, co funkcionalitu návratu z funkce spolehlivě zničí, je přepsání zásobníku na "vhodném" místě nebo prostá změna adresy uložené v registru ESP.
Aby byla procedura co nejuniverzálnější, je nutné její činnost řídit vstupními parametry.
Např. procedura CopyFile by nebyla příliš použitelná, pokud by kopírovala pouze soubor
"C:\Dokument.txt" do "A:\Dokument.txt". Proto např. funkce CopyFile, kterou poskytují Windows,
má tři parametry (jméno zdrojového souboru, jméno cílového souboru a zda má funkce přepsat
cílový soubor, pokud existuje). V praxi je většina procedur v programu s jedním nebo více parametry.
Parametry se do procedury mohou předávat dvěma způsoby (oba se používají i ve vyšších programovacích
jazycích):
Předávání parametrů funkce do zásobníku se provádí sérií instrukcí push, seřazených od posledního parametru k prvnímu. Volání funkce MessageBox, zapsané v C/C++ jako
MessageBox(NULL, szMessage, szTitle, MB_OK);
provedeme takto:
push MB_OK push offset szTitle push offset szMessage push 0 call MessageBoxnebo jednodušeji pomocí direktivy INVOKE, která automaticky vytvoří potřebnou sekvenci instrukcí push, následovanou instrukcí call:
INVOKE MessageBox, NULL, offset szTitle, offset szMessage, MB_OK
Na první instrukci uvnitř funkce MessageBox bude stav zásobníku následující:
Adresa | [ESP] | [ESP+04h] | [ESP+08h] | [ESP+0Ch] | [ESP+10h] |
Obsahuje | RetAddr (Návratová adresa) |
hParent (NULL) |
szMessage | szTitle | nFlags (MB_OK) |
Kdo a jak ale odstraní hodnoty, které jsou do zásobníku vloženy před zavoláním funkce MessageBox ? Závisí to na tzv. volací konvenci funkce:
Volací konvence | Kdo odstraňuje parametry | Jak budou parametry odstraněny |
---|---|---|
_cdecl | Volající, těsně za instrukcí call | Posunutím zásobníku na správnou hodnotu, např. add esp, 10h |
_stdcall | Volaná funkce | Instrukcí ret XX, kde XX je počet parametrů násobený čtyřmi |
Při volání funkce je nutné vědět, kterou volací konvenci musíme použít. Obecně platí, že funkce API Windows používají volací konvenci _stdcall (označovanou také jako WINAPI), a standardní funkce knihovny C používají konvenci _cdecl. V případě, že zvolíme volací konvenci nesprávně, dojde většinou k pádu programu.
Návratová hodnota z funkce je "výsledek" této funkce. Některé funkce vracejí výsledek výpočtu,
jiné zase hodnotu, která informuje o úspěchu nebo neúspěchu činnosti funkce. Není nutné, aby každá
funkce měla návratovou hodnotu - např. funkce, centrující okno buďto skončí s úspěchem nebo s neúspěchem
(např. pokud okno neexistuje).
Návratová hodnota je vracena v registrech procesoru podle následující tabulky:
Velikost vracené hodnoty | Registr |
---|---|
8 bitů (byte, char) | AL |
16 bitů (word, short) | AX |
32 bitů (dword, long) | EAX |
64 bitů (qword, __int64) | EDX (horních 32 bitů) EAX (spodních 32 bitů) |
Než napíšeme proceduru, měli bychom si rozmyslet několik věcí:
Následující příklad ukazuje způsob zápisu procedury a její volání. Jde o funkci, která násobí dvě čísla (v praxi bychom to samozřejmě provedli pomocí jediné instrukce, zde jde o jednoduchý příklad):
.code Multiply PROC dwNumber1 :DWORD, dwNumber2 :DWORD mov eax, dwNumber1 mul dwNumber2 ret Multiply ENDP WinMain PROC hInstance :HANDLE, hPrevInstance:HANDLE, lpszCmdParam :LPSTR, nCmdShow :WORD INVOKE Multiply, 10, 20 ret WinMain ENDP
Některé podrobnosti volání funkcí v assembleru se do dnešní lekce již nevejdou, probereme je příště.
Copyright Ladislav Zezula 2004