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:

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:

Řetězové instrukce pak jsou

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 dej atributy nápisu}
 @cyk:           {cyklus pro znak po znaku}
  
LODSB          {naber 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         {vrať 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

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 dej 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í:

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