Volání podprogramů
V úvodu jsem upozornil na to, že
využití vkládaného assembleru je v tvorbě podprogramů.
Předem si ale musíme ukázat, jak se podprogramy volají.
Volání podprogramu spočívá v
uložení parametrů do zásobníku a změně adresy v registru
IP (čítač instrukcí) na adresu podprogramu s tím, že je
uschována adresa odkud provádíme volání (to aby procesor
věděl kam se má vrátit). Parametry do zásobníku ukládáme
my, zbytek zařídí instrukce CALL.
Ukládání parametrů do
zásobníku
V hlavičce procedury (nebo
funkce) najdeme téměř vždy definici parametrů volaných:
- hodnotou - podprogram jejich
hodnoty pouze využívá
- odkazem - podprogram je
může číst a může do nich i zapsat
Například: procedure
soucet (a,b:word;var c:word);
je definice procedury s názvem součet s parametry a, b
volanými hodnotou a c volaným odkazem. Při volání této
procedury z některé části programu psaném v Pascalu na
místa a, b zapíšeme konkrétní hodnoty (nebo proměnné (ty
ale podprogram nezmění) s těmito hodnotami) a na místo c
zapíšeme proměnnou, ve které najdeme hodnotu po provedení
procedury (např. soucet (1,3,promenna_c); ). Z místa volání předáváme parametry
do podprogramů vždy přes zásobník v pořadí definice v
hlavičce podprogramu. Do zásobníku před voláním procedury
ukládáme odlišně u parametrů volaných hodnotou a odkazem.
- Při volání
hodnotou
Uložíme konkrétní hodnoty (přečtené
třeba i z paměti). Vzhledem k organizaci zásobníku
jsou parametry volané hodnotou uloženy po slovech
následovně:
- parametry o délce
jedné slabiky (byte, shortint, char, boolean) -
obsadí celé slovo (pamětí nešetří)
- parametry o délce
jednoho slova (word, integer) - obsadí slovo
- parametry o délce
dvojslova (pointer, longint) - obsadí dvě slova
(ukazatel je adresa, do zásobníku tedy napřed
uložíme segmentovou a pak offsetovou část
adresy)
- parametry o délce 6
slabik (real) - obsadí v zásovníku tři slova
- parametry delší
(řetězce, množina, pole, záznamy) - se
ukládají jako ukazatele na hodnotu.
- Při volání odkazem
Uložíme celou adresu místa (tedy segment i
offset) odkud se má hodnota číst nebo kam se má
zapsat (to je vlastně obsah ukazatele na paměťové
místo).
Samotné volání podprogramu
Musíme rozlišovat volání
blízkého podprogramu a vzdáleného. Za vzdálený v tomto
případě považujeme podprogram s adresou v odlišném
segmentu. I když se pro programátora nic nemění je dobré
vědět, že při vzdáleném volání se mění nejen IP, ale i
CS. Označení místa skoku nese tedy navíc informaci o
segmentové adrese. Skok do podprogramu zajistí instrukce
- CALL adresa
- na vrchol zásobníku ulož obsah (CS při vzdáleném
volání a) IP a naplň tyto registry adresou uvedenou v
parametru (pro nás slovo adresa nahradíme názvem
podprogramu)
Ukončení samotného podprogramu
zajistí instrukce
- RET[F]
- z vrcholu zásobníku vezmi adresy a dosaď je do (CS
a) IP Volání podprogramů je tedy jednoduché.
Jednoduše napíšeme instrukci CALL
se jménem podprogramu (tedy procedury nebo funkce). Ostatní
zařídí překladač, který zjistí, jestli se jedná o
blízké nebo vzdálené volání. Podle toho dosadí adresu.
Návrat si opět zařídí překladač při ukončení
podprogramu.
Příklad:
{$G+}
uses crt;
procedure pocitej (a,b:word;var c,d:word);
begin
c:=a+b;
d:=a-b;
end;
var a_,b_,c_,d_:word;
begin
a_:=40;
b_:=5;
clrscr;
asm
PUSH a_ {proceduře posíláme hodnotu a_}
PUSH b_ {proceduře posíláme hodnotu b_}
LEA DI,c_ {zjistíme adresu proměnné c_}
PUSH DS {do zásobníku segment adresy c_}
PUSH DI {do zásobníku offset adresy c_}
LEA DI,d_ {to samé pro d_}
PUSH DS {stejný segment}
PUSH DI {offset d_}
CALL pocitej{a zavoláme počítej}
end;
writeln (a_,'+(-)',b_,'=',c_,'(',d_,')');
readkey;
end.
Stejnou posloupnost instrukcí
jako blok asm v tomto programu provede řádek počítej
(a_,b_,c_,d_);
Návrat hodnoty z funkce
Funkce je podprogram, který
vrací jednu hodnotu typu uvedeného v záhlaví. Vracenou
hodnotu zjistíme po návratu z funkce vždy v registrech:
- AL - funkční hodnota o
velikosti slabiky
- AX - funkční hodnota o
velikosti slova
- DX, AX - funkční hodnota o
velikosti dvojslova (u ukazatele DX - segment, AX -
offset)
- DX, BX, AX - funkční
hodnota typu real
Pokud funkce vrací řetězec,
musí být volána i s adresou místa, kam má výsledný
řetězec zapsat.
Příklad:
{$G+}
uses crt;
function bez1 (a:word):word;
begin
bez1:=a-1;
end;
var a_,c_:word;
begin
a_:=40;
clrscr;
asm
PUSH a_ {posíláme hodnotu a_}
CALL bez1 {zavoláme }
MOV c_,AX {slovo si vyzvedneme v registru AX}
end;
writeln (a_,'-1=',c_);
readkey;
end.
|