Skoky a podprogramy - M8C: Rozdiel medzi revíziami

Z Kiwiki
Skočit na navigaci Skočit na vyhledávání
Riadok 1: Riadok 1:
 +
=Podprogramy=
 
Volanie podprogramu spočíva v uložení parametrov do zásobníka a zmene adresy v registri 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.
 
Volanie podprogramu spočíva v uložení parametrov do zásobníka a zmene adresy v registri 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.
  

Verzia zo dňa a času 21:09, 21. jún 2010

Podprogramy

Volanie podprogramu spočíva v uložení parametrov do zásobníka a zmene adresy v registri 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:

  • hodnotou - podprogram ich hodnoty iba využíva
  • odkazom - podprogram ich môže čítať a môže do nich i zapísať

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.

Pri volaní hodnotou

Uložíme konkrétne hodnoty (prečítané napr. i z pamäti). Vzhľadom k organizácii zásobníka sú parametre volané hodnotou uložené po slovách nasledovne:

  • parametre o dĺžke jednej slabiky (byte, short int, char, bool) - obsadia celé slovo (pamäťou nešetria)
  • parametre o dĺžke jedného slova (word, int) - obsadia slovo
  • parametre o dĺžke dvojslova (pointer, long int) - obsadia dve slova (ukazovateľ je adresa, do zásobníka teda najskôr uložíme segmentovú a potom offsetovú časť adresy)
  • parametre o dĺžke 6 slabík (float) - obsadia v zásobníku tri slová
  • parametre dlhšie (reťazce, množina, pole, záznamy) - sa ukladajú ako ukazovatele na hodnotu.

Pri volaní odkazom

Uložíme celú adresu miesta (teda segment i offset) odkiaľ sa má hodnota čítať alebo kam sa má zapísať (to je vlastne obsah ukazovateľa na pamäťové miesto).

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

CALL

adresa - na vrchol zásobníka ulož obsah (CS pri vzdialenom volaní a) IP a naplň tieto registre adresou uvedenou v parametri (pre nás slovo adresa nahradíme názvom podprogramu)

Ukončení samotného podprogramu zaistí inštrukcia

RET[F]

z vrcholu zásobníka vezmi adresy a dosaď ich do (CS a) IP Volanie podprogramov je teda jednoduché.

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:

  • AL - funkčná hodnota o veľkosti slabiky
  • AX - funkčná hodnota o veľkosti slova
  • DX, AX - funkčná hodnota o veľkosti dvojslova (pri ukazovateli DX - segment, AX - offset)
  • DX, BX, AX - funkčná hodnota typu float

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();
}

Skoky

Nepodmienený skok

Je to nepodmienený skok na iné miesto programu. To musí byť označené návestím. Za inštrukciou skoku je potom uvedený jeho názov.

JMP

navestie - urob skok programu na návestie (v skutočnosti sa len zmení obsah čítača inštrukcií IP, prípadne CS pri vzdialenom skoku) V programe potom nepodmienený skok vyzerá takto:

navestie: inštrukcia na ktorú bude odkaz
          .
          .
          JMP navestie

Ak skoky používame, hrozí vždy nebezpečie, že sa program zacyklí (a nikdy neskončí). Preto je dôležité si vždy rozmyslieť, za akých okolností by k tejto kolízii mohlo dôjsť.

Podmienený skok

Jedná sa o skok podmienený stavom jedného alebo viac, bitov registri príznakov F. Jen týmto spôsobom je možné prevádzať v assembleri priame vetvenie programu. Pred inštrukciou podmieneného skoku preto vždy prevedieme inštrukciu, ktorá použitý príznak nastaví. V prípade, že nie je splnená podmienka skoku, pokračuje program ďalej, ako by sa nič nedialo. Inštrukcie podmieneného skoku začínajú vždy písmenkom J. Za ním je skratka udávajúca na akých bitoch registru F je skok závislý.


  • JE návestie - skok na návestie pri ZF = 1
  • JZ návestie - skok na návestie pri ZF = 1
  • JNE návestie - skok na návestie pri ZF = 0
  • JNZ návestie - skok na návestie pri ZF = 0
  • JC návestie - skok na návestie pri CF = 1
  • JNC návestie - skok na návestie pri CF = 0
  • JS návestie - skok na návestie pri SF = 1
  • JNS návestie - skok na návestie pri SF = 0
  • JO návestie - skok na návestie pri OF = 1
  • JNO návestie - skok na návestie pri OF = 0
  • JP návestie - skok na návestie pri PF = 1
  • JNP návestie - skok na návestie pri PF = 0
  • JPE návestie - skok na návestie pri PF = 1
  • JPO návestie - skok na návestie pri PF = 0
  • JA návestie - skok na návestie pri (CF = 0) AND (ZF = 0)
  • JNBE návestie - skok na návestie pri (CF = 0) AND (ZF = 0)
  • JAE návestie - skok na návestie pri CF = 0
  • JNB návestie - skok na návestie pri CF = 0
  • JB návestie - skok na návestie pri CF = 1
  • JNAE návestie - skok na návestie pri CF = 1
  • JBE návestie - skok na návestie pri (CF = 1) OR (ZF = 1)
  • JNA návestie - skok na návestie pri (CF = 1) OR (ZF = 1)
  • JG návestie - skok na návestie pri (ZF = 0) OR (SF = OF)
  • JNLE návestie - skok na návestie pri (ZF = 0) OR (SF = OF)
  • JGE návestie - skok na návestie pri SF = OF
  • JNL návestie - skok na návestie pri SF = OF
  • JL návestie - skok na návestie pri SF <> OF
  • JNGE návestie - skok na návestie pri SF <> OF
  • JLE návestie - skok na návestie pri (ZF = 1) OR (SF <> OF)
  • JNG návestie - skok na návestie pri (ZF = 1) OR (SF <> OF)


Pri hľadaní inštrukcie podmieneného skoku musíme myslieť na to, za akých okolností chceme skok vykonať. K tomu je tiež dobré si uvedomiť:

  • A < B => A - B < 0 => SF = 1
  • A = B => A - B = 0 => ZF = 1
  • A > B => A - B > 0 => SF = 0

Rozdiel čísel v tomto prípade prevedieme najlepšie inštrukciou CMP. Pre tvorbu cyklu môžeme použiť jeden z registrov, ktorý si pre krokovaciu premennú vyčleníme. Jednoduchý cyklus potom vytvoríme podmieneným skokom:


main() {
  asm MOV CL, 10  // do registra CL dosaď 10, počet krokov
  nav:            // návestie, tu umiestnime opakovanú činnosť
  asm {
    DEC CL        // odčítaj od CL číslo 1
    JNZ nav       // ak nie je nula skoč na návestie
  }
}

Program opakuje skok dokiaľ nie je v registri CL nulový výsledok.