Volanie podprogramov


<= =>


V úvodu som upozornil na to, že využitie vkladaného assembleru je v tvorbe podprogramov. Najskôr si ale musíme ukázať, ako sa podprogramy volajú.

Volaní podprogramu spočíva v uložení parametrov do zásobníka a zmene adresy v registru IP (čítač inštrukcií) na adresu podprogramu s tým, že je uschovaná adresa odkiaľ prevádzame volanie (to aby procesor vedel kam sa má vrátiť). Parametre do zásobníku ukladáme my, zvyšok zariadi inštrukcia CALL.

Ukladanie parametrov do zásobníka

V hlavičke procedúry (alebo funkcie) nájdeme takmer vždy definíciu parametrov volaných: Napríklad: void sucet (word a, word b, word &c) je definícia procedúry s názvom súčet s parametrami a, b volanými hodnotou a c volaným odkazom. Pri volaní tejto procedúry z niektorej časti programu písanom v C++ na miesta a, b zapíšeme konkrétne hodnoty (alebo premenné (tie ale podprogram nezmení) s týmito hodnotami) a na miesto c zapíšeme premennú, v ktorej nájdeme hodnotu po prevedení procedúry (napr. sucet (1,3,premenna_c);). Z miesta volania predávame parametre do podprogramov vždy cez zásobník v opačnom poradí ako je v definícii hlavičky podprogramu (prvá premenná bude na vrchole zásobníka). Do zásobníka pred volaním procedúry ukladáme odlišne parametre volané hodnotou a odkazom.

Samotné volanie podprogramu

Musíme rozlišovať volanie blízkeho podprogramu a vzdialeného. Za vzdialený v tomto prípade považujeme podprogram s adresou v odlišnom segmente. I keď sa pre programátora nič nemení je dobré vedieť, že pri vzdialenom volaní sa mení nie len IP, ale i CS. Označenie miesta skoku nesie teda naviac informáciu o segmentovej adrese. Skok do podprogramu zaistí inštrukcia Ukončení samotného podprogramu zaistí inštrukcia Jednoducho napíšeme inštrukciu CALL s menom podprogramu (teda procedúry alebo funkcie). Ostatné zariadi prekladač, ktorý zistí, či sa jedná o blízke alebo vzdialené volanie. Podľa toho dosadí adresu. Návrat si opäť zariadi prekladač pri ukončení podprogramu.

Za volaním programu musíme uvolniť zásobník, teda musíme vybrať toľko hodnôt, koľko sme ich vložili pred volaním podprogramu (počet parametrov podprogramu).

#include <conio.h>
#include <stdio.h>

typedef unsigned int word;
word wa,wb,wc,wd;

void pocitaj(word a, word b, word &c, word &d) {
  c = a + b;
  d = a - b;
}

void *proc_pocitaj=pocitaj;

void main() {
  wa = 40;
  wb = 5;
  clrscr();
  asm {
    LEA DI,wd           // zistíme adresu premennej wd
    PUSH DI             // offset premennej wd
    LEA DI,wc           // zistíme adresu premennej wc
    PUSH DI             // offset premennej wc
    PUSH wb             // procedúre posielame hodnotu wb
    PUSH wa             // procedúre posielame hodnotu wa
    CALL proc_pocitaj   // a zavoláme pocitaj
    MOV cx, 4           // do zásobníka sme vložili 4 hodnoty
    REP POP ax          // zásobník musíme vyprázdniť
  }
  printf("%d+(-)%d=%d(%d)", wa,wb,wc,wd);
  getchar();
}

Rovnakú postupnosť inštrukcií ako blok asm v tomto programe prevedie riadok pocitaj(aa,bb,cc,dd);

Návrat hodnoty z funkcie

Funkcia je podprogram, ktorý vracia jednu hodnotu typu uvedeného v záhlaví. Vracanú hodnotu zistíme po návrate z funkcie vždy v registroch: Pokiaľ funkcia vracia reťazec, musí byť volaná i s adresou miesta, kam má výsledný reťazec zapísať.
#include <conio.h>
#include <stdio.h>
#include <process.h>

typedef unsigned int word;
word wa, wc;
word minus(word a) {
  return (a-1);
}

void *funk_minus = minus;

void main() {
  wa = 40;
  clrscr();
  asm {
    PUSH wa            // posielame hodnotu wa
    CALL funk_minus    // zavoláme funkciu
    MOV wc, AX         // slovo si vyzdvihneme v registri AX
    POP AX             // vyprázdnime zásobník
  }
  printf("\n%d-1=%d",wa,wc);
  getchar();
}



<= =>