Výuka assembleru
8. Kombinování assembleru a C/C++
Jestliže potřebujeme do programu začlenit časově kritickou funkci, je možné tuto funkci napsat v assembleru a přidat ji do projektu. Chceme napsat např. funkci pro zjišťování délky řetězce. Tato funkce je v C k dispozici (strlen), ale pro názornost ji napíšeme sami. Řetězec bude předán podle céčkovských zvyklostí ukazatelem na začátek řetězce. Konec řetězce poznáme podle znaku z kódem 0 (který se do délky nepočítá).
Většina překladačů C++ umožňuje psát assembler přímo do zdrojového textu. Tato varianta
míchání C a assembleru je jednodušší než napsání modulu v assembleru (který je navíc potřeba
zařadit do projektu speciálním způsobem).
Pro zápis assembleru v C je nutné použít příkaz _asm:
_asm
{
mov eax, 0
mov ebx, eax
}
Tímto způsobem můžeme napsat kostru funkce (návratová hodnota a parametry) v C, a tělo funkce napsat v assembleru. Nejsou žádné starosti s volací konvencí, typy parametrů ani s linkováním funkce do projektu. Funkci pro spočítání délky řetězce napíšeme takto:
int StringLength(char * szString)
{
int strlength = 0;
_asm
{
mov esi, szString; // Adresa řetězce
mov ecx, 0 // Počet znaků
__LoadChar:
mov al, [esi]; // Do AL načteme první/další znak
cmp al, 0 // Je to konec řetězce ?
jz __EndString // Ano => skok
inc esi // Ukazatel v ESI přesuneme na další znak
inc ecx // Zvýšíme počet znaků o 1
jmp __LoadChar
__EndString:
mov strlength, ecx // Uložíme délku řetězce
}
return strlength;
}
void main(void)
{
int length = StringLength("Toto je pokusný řetězec pro assembler");
printf("Delka retezce je %i\n", length);
}
Druhý způsob vyžaduje zařazení funkce do samostatného modulu, který bude kompletně napsán v assembleru. Nejdříve si ukážeme céčkovskou část:
#include <stdio.h>
extern "C" int _cdecl StringLength(char * szString);
void main(void)
{
int length = StringLength("Toto je pokusný řetězec pro assembler");
printf("Delka retezce je %i\n", length);
}
Není zde nic zvláštního - pro nás neobvyklá je deklarace funkce s extern "C"
(která je potřeba k tomu, aby překladač nedoplňoval ke jménu funkce dekoraci z C++),
a dále klíčové slovo _cdecl, které zdůrazňuje volací konvenci CDECL, která se používá
v programovacím jazyce C.
Modul v assembleru bude vypadat takto:
.586
.MODEL FLAT,STDCALL
OPTION CASEMAP:NONE
include ../inc/windows.inc
.code
StringLength PROC C szString:LPSTR
push esi ; Uložíme obsah registru ESI
mov esi, szString ; Adresa řetězce
mov eax, 0 ; Počet znaků
__LoadChar:
mov cl, [esi] ; Do AL načteme první/další znak
cmp cl, 0 ; Je to konec řetězce ?
jz __EndString ; Ano => skok
inc esi ; Ukazatel v ESI přesuneme na další znak
inc eax ; Zvýšíme počet znaků o 1
jmp __LoadChar
__EndString:
; Návratová hodnota (počet znaků) je v EAX
pop esi ; Obnovíme obsah registru ESI
ret
StringLength ENDP
end
Všimněte si deklarace PROC C za jménem funkce StringLength. Tato deklarace říká, že
funkce bude používat volací konvenci _cdecl. Volací konvence u funkce z assembleru se
musí shodovat s volací konvencí nadeklarovanou v céčkovském modulu, jinak bude program padat.
Funkce také ukládá a obnovuje obsah registru ESI; Obsah registrů ESI a EDI
je lepší ukládat, protože volající funkce někdy předpokládá, že nebudou uvnitř funkce změněny.
Po vložení assemblerovského modulu do projektu ve Visual C++ je nutné nastavit překlad tohoto modulu, protože Visual C++ to implicitně neumí. Jakmile vložíte assemblerovský modul do projektu, nastavte v "Project\Settings", soubor StrLen.asm, karta "Custom Build" takto:
Pokud znáte assembler trochu lépe a zdá se vám, že celá funkce by se dala ještě více zoptimalizovat, máte pravdu. K optimalizaci se ale dostaneme v některém z příštích dílů.
Copyright Ladislav Zezula 2004