Inštrukcie pre prácu s reťazcami


<= =>


ASM86 má veľmi silný nástroj v reťazcových inštrukciách. Za reťazec je tu na rozdiel od Pascalovského považovaný blok dát v pamäti o skoro ľubovolnej dĺžke (podľa definície sme obmedzení len veľkosťou segmentu, to sa ale dá jednoducho obísť). Pre použitie reťazcových inštrukcií sú vyčlenené dvojice registrov, ktoré nesú adresy:

V praxi to znamená, že vždy jeden blok v pamäti je označený za zdrojový, druhý za cieľový. Dôležitou roľu tu hrajú i registre: Reťazcové inštrukcie potom sú Slovo zvýšiť v týchto popisoch činnosti nahradíme slovom znížiť pri DF = 1. Tieto inštrukcie umožnia naraz previesť určitú činnosť a pritom aktualizujú adresy podľa stavu DF a podľa toho, či pracujeme so slabikami alebo slovami.

Nasledujúci príklad využíva priamy zápis do videopamäti (VRAM) v textovom režime VGA k výstupu reťazca. VRAM, začína na adrese 0xB8000. Je organizovaná ako pole slov nesúce informácie o zobrazovaných znakoch. Každé slovo nesie slabiku atribútov (farba znaku a jeho pozadie) a slabiku s ASCII kódom zobrazeného znaku. 80 slov VRAM je jeden riadok na obrazovke. Preto pri zvýšení adresy 0xB8000:0x0000 o 160 môžeme pracovať s druhým riadkom atd.

#include <conio.h>
#include <string.h>
char slovo[254]="Ahoj";   // dĺžka reťazca na začiatku
unsigned char dlzka;
void main() {
  dlzka = strlen(slovo);
  clrscr();
  asm {
    PUSH DS             // ulož obsah DS do zásobníka, budeme ho meniť
    JMP dal             // obídi dáta
    label vram dword
    DW 0x0000,0xB800    // offset:segment VRAM, Pozor! je to obrátene
    label adsl dword
    DD slovo            // adresa slova, ukazovateľ na neho
  }
  dal:                  // začiatok programu
  asm {
    LDS SI, CS:[OFFSET adsl]  // DS:SI nasmeruj na zdroj (na slovo)
    LES DI, CS:[OFFSET vram]  // ES:DI nesmeruj na VRAM
    XOR CH,CH                 // nuluj CH
    MOV CL,dlzka              // do CL daj dĺžku reťazca slovo, 1. slabiku
    MOV AH,0x6F               // do AH daj atribúty nápisu
  }
  cyk:         // cyklus pre znak po znaku
  asm {
    LODSB      // naber kód znaku z reťazca do AL a zvýš SI+1
    STOSW      // ulož obsah AX do VRAM, zvýš DI+2
    LOOP cyk   // zníž CX o jednu, ak nie je nula choď na cyk
    POP DS     // vráť register DS do pôvodného stavu
  }
}

Uvedený program zmení slabiku na slovo v registru AX s tým, že bude kód znaku doplnený o atribúty. Ak zmeníme hodnotu v AH ovplyvníme tým farbu výstupu.

Prefix opakovania

Doteraz poznáme len prefix preskočenia. Prefix opakovania sa používa pred reťazcovými inštrukciami a umožňuje tak ich podmienené i nepodmienené opakovanie. Ich použitím zrýchlime a zjednodušíme program. Nepodmieneným prefixom je Tento prefix píšeme väčšinou pred inštrukciu MOVSB (MOVSW). Ak máme nastavený register CX na počet prvkov reťazca a adresové registre zdrojového a cieľového reťazca, zaistí REP ich skopírovanie na jednom riadku programu (napr. REPMOVSB).
#include <stdio.h>
char slovo1[254]=”Ahoj”, slovo2[254];
unsigned char dlzka;
main() {
  dlzka = strlen(slovo1);
  asm {
    PUSH DS           // ulož do zásobníka obsah DS, zmeníme ho
    JMP dal           // skoč na začiatok, obídi dáta
    label adr dword
    DD slovo1,slovo2  // definícia ukazovateľov na pole
  }
  dal:
  asm {
    LDS SI,CS:[OFFSET adr]   // naber adresu zdrojového reťazca
    LES DI,CS:[OFFSET adr+4] // naber adresu cieľového reťazca
    XOR CH,CH                // nuluj CH
    MOV CL,dlzka             // do CL daj dĺžku reťazca
    REP MOVSB                // kopíruj reťazce po slabikách
    POP DS                   // vráť obsah DS zo zásobníka
  }
  printf(“\n%s %s”, slovo1, slovo2);
  getchar();
}

V príklade kopírujeme jen toľko prvkov, koľko má zdrojové slovo slabík. Túto informáciu si zistíme funkciou strlen. Aj keď všetky presuny sa odohrávajú v dátovom segmente s adresou v DS, je dobré si zvyknúť na to, že vždy, keď meníme DS, ukladáme jeho obsah pre istotu do zásobníka.

Reťacové inštrukcie vyhľadania a porovnania využívajú register príznakov ZF. Preto ASM86 obsahuje naviac prefixy podmieneného opakovania:

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

unsigned int pole[10];
unsigned int hladany, pozicia;
unsigned char i;

void main() {
  clrscr();
  randomize();

  for (i=0; i<=9;i++)
    pole[i]=random(32767);   // do poľa náhodné čísla

  hladany=pole[random(10)];  // vyber hľadané číslo
  printf("Hladam: %d",hladany);

  asm {
    JMP zac    // skok na začiatok
    label adr dword
    DD pole    // definícia ukazovateľa na pole
  }
  zac:
  asm {
    MOV AX, hladany          // do AX vlož hľadané číslo
    MOV CX, 10               // do CX vlož dĺžku reťazca (pole)
    LES DI, CS:[OFFSET adr]  // naber adresu reťazca
    REPNE SCASW              // opakuj do zhody porovnania
    MOV pozicia, 9           // spočítaj koľký je hľadaný,
    SUB pozicia,CX           // k tomu použiješ to, čo zostalo v CX
  }
  for (i=0; i<=9; i++) {
    if (i!=pozicia)
      textcolor(15);
    else
      textcolor(12);
    cprintf("\n\r%d", pole[i]);
  }
  getch();
}

Tento program vyhľadá slovo v poli. K tomu slúži len riadok REPNE SCASW. Ten opakuje pohyb po poli, dokiaľ nenájde zhodu s hodnotou v registri AX (tá sa prejaví nastavením ZF do 1) . K zisteniu pozície hľadaného dobre poslúži zvyšok v registri CX. Keby bol zvyšok nulový, hľadaný prvok by v poli nebol.

#include <string.h>
#include <conio.h>

char slovo1[254]="Nazdar programatori! Skuste vyhladat nejake slovo z tejto vety.";
char slovo2[254]="slovo";
unsigned int i, miesto, dlzka1, dlzka2;

void main () {
  dlzka2 = strlen(slovo2);
  dlzka1 = strlen(slovo1);
  asm {
    PUSH DS                   // ulož DS, budeme ho meniť
    JMP dal                   // preskoč dáta
    label ukp dword
    DD slovo1, slovo2         // ukazovatele na reťazce
  }
  dal:
  asm LDS SI,CS:[OFFSET ukp]  // naber adresu zdroja
  cyk:
  asm {
    LES DI,CS:[OFFSET ukp+4]  // naber adresu cieľa, hľadaného slova
    MOV CX, dlzka2            // do CX vlož dĺžku reťazca
    REPE CMPSB                // opakuj do nezhody (konca hľadaného)
    JZ koniec                 // ak bola zhoda tak na koniec
    SUB SI, dlzka2            // ak ale bola zhoda tak sa v SI vráť
    INC SI
    ADD SI,CX                 // pre návratu v SI použi zvyšok v CX
    JMP cyk                   // a znovu hľadať
  }
  koniec:
  asm {
    POP DS                            // vráť obsah DS, už ho nebudeme meniť
    MOV miesto,SI                     // vypočítaj miesto v prehľadávanom
    MOV SI, word ptr cs:[OFFSET ukp]  // k tomu použiješ dĺžku reťazca zdroja
    ADD SI, dlzka2                    // dĺžku cieľa, teda hľadaného
    SUB miesto,SI
  }
  clrscr();
  for (i=0; i<=dlzka1; i++) {
    if (!((i>=miesto) && (i<=miesto+dlzka2-1)))
      textcolor(15);
    else
      textcolor(12);
    cprintf("%c",slovo1[i]);
  }
  getch();
}

V príklade prehľadávame reťazec slovo1. Hľadáme v ňom umiestnenie podreťazca slovo2. Program má dva cykly v sebe. Prvý zaisťuje pohyb po prehľadávanom reťazci v prípade nezhody (je realizovaný JMP). Druhý vnútorný zaisťuje pohyb po prehľadávanom s kontrolou s hľadaným (je realizovaný REPE). V prípade zhody je po cyklu REPE v registru ZF = 1 (proste nevyskočil nezhodou ale nulou v CX => koniec hľadaného slova a zhoda). Preto cyklus prehľadávania ukončíme podmieneným skokom JZ na koniec. Tu sa zistí adresa v prehľadávanom reťazci. To je ale adresa za posledným znakom zhody. Preto sa vrátime naspäť o dĺžku slova (tam je hľadané slovo).


<= =>