Učebnice Assembleru 86

Instrukce pro práci s řetězci

ASM86 má velmi silný nástroj v řetězcových instrukcích. Za řetězec je zde na rozdíl od Pascalovského považován blok dat v paměti o téměř libovolné délce (podle definice jsme omezeni jen velikostí segmentu, to se ale dá snadno obejít). Pro použití řetězcových instrukcí jsou vyčleněny dvojice registrů, které nesou adresy:

  • DS:SI - pro adresu zdrojového řetězce
  • ES:DI - pro adresu cílového řetězce

V praxi to znamená, že vždy jeden blok v paměti je označen za zdrojový, druhý za cílový. Důležitou roli zde hrají i registry:

  • CX - nese délku řetězce
  • DF - určuje směr zpracování řetězců (0 - adresy se zvyšují, 1 - adresy se snižují)

Řetězové instrukce pak jsou

  • LODSB (LODSW) - přesuň z adresy DS:SI do registru AL (AX) a zvyš SI o jednu (o dvě)
  • STOSB (STOSW) - přesuň z registru AL (AX) na adresu ES:DI a zvyš DI o jednu (o dvě)
  • MOVSB (MOVSW) - přesuň z adresy DS:SI slabiku (slovo) na adresu ES:DI a SI, DI zvyš o jednu (o dvě)
  • CMPSB (CMPSW) - porovnej (odečti) slabiku (slovo) na adrese DS:SI se slabikou (slovem) na adrese ES:DI, podle výsledku nastav příznaky (ZF = 1 při shodě, ZF = 0 při neshodě), potom zvyš adresy SI a DI o jednu (o dvě)
  • SCASB (SCASW) - porovnej (odečti) slabiku z adresy ES:DI z registrem AL (AX), podle výsledku nastav příznaky (ZF = 1 při shodě, ZF = 0 při neshodě), potom zvyš adresu DI o jednu (o dvě)
  • INSB (INSW) - [286], přesuň z portu s adresou v DX do paměti s adresou ES:DI slabiku (slovo) a adresu DI zvyš o jednu (o dvě)
  • OUTSB (OUTSW) - [286], přesuň z paměti s adresou DS:SI slabiku (slovo) na port určený adresou v DX a zvyš adresu SI o jednu (o dvě)

Slovo zvýšit v těchto popisech činnosti nahradíme slovem snížit při DF = 1. Tyto instrukce umožní najednou provést určitou činnost a přitom aktualizují adresy podle stavu DF a podle toho, jestli pracujeme se slabikami nebo slovy.

Následující příklad využívá přímého zápisu do videopaměti (VRAM) v textovém režimu VGA k výstupu pascalovského řetězce. VRAM, začíná na adrese $B8000. Je organizovaná jako pole slov nesoucích informace o zobrazovaných znacích. Každé slovo nese slabiku atributů (barva znaku a jeho pozadí) a slabiku s ASCII kódem zobrazeného znaku. 80 slov VRAM je jeden řádek na obrazovce. Proto při zvýšení adresy $B8000 o 160 můžeme pracovat s druhým řádkem atd.

Příklad:

var slovo:string;
begin
 slovo:='Ahoj';
 asm
  PUSH DS       {ulož obsah DS do zásobníku, budeme ho měnit}
  JMP @dal      {obejdi data}
 @vram:
  DW $0000,$B800{offset:segment VRAM, Pozor! je to obráceně}
 @adsl:
  DD slovo      {adresa slova, ukazatel na něj}
 @dal:          {začátek programu}
  LDS SI,CS:[OFFSET @adsl]{DS:SI nasměruj na zdroj (na slovo)}
  LES DI,CS:[OFFSET @vram]{ES:DI nesměruj na VRAM}
  XOR CH,CH     {nuluj CH}
  MOV CL,[SI]   {do CL dej délku řetězce slovo, 1. slabiku}
  INC SI        {posuň se za slabiku s délkou}
  MOV AH,$6F    {do AH vlož atributy nápisu}
 @cyk:          {cyklus pro znak po znaku}
  LODSB         {získej kód znaku z řetězce do AL a zvyš SI+1}
  STOSW         {ulož obsah AX do VRAM, zvyš DI+2}
  LOOP @cyk     {sniž CX o jednu, není-li nula jdi na @cyk}
  POP DS        {obnov registr DS do původního stavu}
 end;
end.

Uvedený program změní slabiku na slovo v registru AX s tím, že bude kód znaku doplněn o atributy. Jestliže změníme hodnotu v AH ovlivníme tím barvu výstupu.

Prefix opakování

Dosud známe jen prefix přeskočení. Prefix opakování se používá před řetězcovými instrukcemi a umožňuje tak jejich podmíněné i nepodmíněné opakování. Jejich použitím zrychlíme a zjednodušíme program. Nepodmíněným prefixem je

  • REP instrukce - opakuj instrukci tolikrát, kolik je uvedeno v registru CX (CX := CX - 1, opakuj dokud CX <> 0)

Tento prefix píšeme většinou před instrukci MOVSB (MOVSW). Jestliže máme nastavený registr CX na počet prvků řetězce a adresové registry zdrojového a cílového řetězce, zajistí REP jejich zkopírování na jednom řádku programu (např. REP MOVSB).

Příklad:

var slovo1,slovo2:string;
begin
 slovo1:='Ahoj';
 asm
  PUSH DS          {ulož do zásobníku obsah DS, změníme ho}
  JMP @dal         {skoč na začátek, obejdi data}
 @adr:
  DD slovo1,slovo2 {definice ukazatelů na pole}
 @dal:
  LDS SI,CS:[OFFSET @adr]  {naber adresu zdrojového řetězce}
  LES DI,CS:[OFFSET @adr+4]{naber adresu cílového řetězce}
  XOR CH,CH        {nuluj CH}
  MOV CL,[SI]      {do CL vlož délku řetězce}
  INC CX           {pascalovský řetězec nese o slabiku více}
  REP MOVSB        {kopíruj řetězce po slabikách}
  POP DS           {vrať obsah DS ze zásobníku}
 end;
 writeln (slovo1,' ',slovo2);
 readln;
end.

V příkladu kopírujeme jen tolik prvků, kolik má zdrojové slovo slabik. Tuto informaci si zjistíme z první slabiky proměnné slovo1. K tomu musíme ještě přičíst 1, protože pascalovský řetězec nese navíc informaci o délce. I když veškeré přesuny se odehrávají v datovém segmentu s adresou v DS, je dobré si zvyknout na to, že vždy, když měníme DS, ukládáme jeho obsah pro jistotu do zásobníku.

Řetězcové instrukce vyhledání a porovnání využívají registr příznaků ZF. Proto ASM86 obsahuje navíc prefixy podmíněného opakování:

  • REPE instrukce <=> REPZ instrukce - opakuj tolikrát, kolik je v registru CX a dokud je ZF = 1 (CX := CX - 1, zopakuj pokud je (CX <> 0) AND (ZF = 1))
  • REPNE instrukce <=> REPNZ instrukce - opakuj tolikrát, kolik je v registru CX a dokud je ZF = 0 (CX := CX - 1, zopakuj pokud je (CX <> 0) AND (ZF = 0)) Opakování je tedy přerušeno nejen při nulovém CX, ale i při nastavení ZF do log. 1 nebo 0.

Příklad:

uses crt;
var pole:array [0..9] of word;
    hledany,pozice:word;
    i:byte;
begin
 clrscr;
 randomize;
 for i:=0 to 9 do
  pole[i]:=random(65535);   {do pole náhodná čísla}
 hledany:=pole[random(10)]; {vyber hledané číslo}
 writeln ('Hledam:',hledany);
 asm
  JMP @zac                  {skok na začátek}
 @adr:
  DD pole                   {definice ukazatele na pole}
 @zac:
  MOV AX,hledany            {do AX vlož hledané číslo}
  MOV CX,10                 {do CX vlož délku řetězce (pole)}
  LES DI,CS:[OFFSET @adr]   {naber adresu řetězce}
  REPNE SCASW               {opakuj do shody porovnání}
  MOV pozice,9              {spočítej kolikátý je hledaný}
  SUB pozice,CX             {k tomu použiješ to, co zbylo v CX}
 end;
 for i:=0 to 9 do
 begin
  if i<>pozice then textcolor(15) else textcolor(12);
  writeln (pole[i]);
 end;
 readkey;
end.

Tento program vyhledá slovo v poli. K tomu slouží jen řádek REPNE SCASW. Ten opakuje pohyb po poli, dokud nenajde shodu s hodnotou v registru AX (ta se projeví nastavením ZF do 1) . K zjištění pozice hledaného dobře poslouží zbytek v registru CX. Kdyby byl zbytek nulový, hledaný prvek by v poli nebyl.

Příklad:

uses crt;
var slovo1,slovo2:string;
    ukazatel:pointer;
    i,misto,delka:word;
begin
 slovo1:='Nazdar programátoři! '+
         'Zkuste vyhledat nějaké slovo z této věty.';
 slovo2:='slovo';
 delka:=length(slovo2);
 asm
  PUSH DS                  {ulož DS, budeme ho měnit}
  JMP @dal                 {přeskoč data}
 @ukp:
  DD slovo1,slovo2         {ukazatele na řetězce}
 @dal:
  LDS SI,CS:[OFFSET @ukp]  {naber adresu zdroje}
  INC SI                   {přeskoč délku řetězce}
 @cyk:
  LES DI,CS:[OFFSET @ukp+4]{naber adresu cíle, hledaného slova}
  INC DI                   {přeskoč slabiku s délkou řetězce}
  MOV CX,delka             {do CX vlož délku řetězce}
  REPE CMPSB               {opakuj do neshody (konce hledaného)}
  JZ @konec                {byla shoda, tak na konec}
  SUB SI,delka             {nebyla shoda tak se v SI vrať}
  INC SI
  ADD SI,CX                {k návratu v SI použij zbytek v CX}
  JMP @cyk                 {a znovu hledat}
 @konec:
  POP DS                   {vrať obsah DS, už ho nebudeme měnit}
  MOV misto,SI             {vypočítej místo v prohledávaném}
  MOV SI,CS:[OFFSET @ukp]  {k tomu použiješ délku řetězce zdroje}
  ADD SI,delka             {délku cíle, tedy hledaného}
  SUB misto,SI
 end;
 clrscr;
 for i:=1 to length(slovo1) do
 begin
  if not(i in [misto..misto+delka-1]) then
  textcolor (15)
                                      else 
  textcolor(12);
  write(slovo1[i]);
 end;
 readkey;
end.

V příkladu prohledáváme řetězec slovo1. Hledáme v něm umístění podřetězce slovo2. Program má dva cykly v sobě. První zajišťuje pohyb po prohledávaném řetězci v případě neshody (je realizován JMP). Druhý vnitřní zajišťuje pohyb po prohledávaném s kontrolou s hledaným (je realizován REPE). V případě shody je po cyklu REPE v registru ZF = 1 (prostě nevyskočil neshodou ale nulou v CX=> konec hledaného slova a shoda). Proto cyklus prohledávání ukončíme podmíněným skokem JZ na konec. Zde ze zjistí adresa v prohledávaném řetězci. To je ale adresa za posledním znakem shody. Proto se vrátíme nazpátek o délku slova (tam je hledané slovo).

Směr