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