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).
|