LEKCJA 17: TROCH� SZCZEG�L�W TECHNICZNYCH. ________________________________________________________________ W trakcie tej lekcji dowiesz si� wi�cej o szczeg�ach dzia�ania komputera widzianych z poziomu assemblera. ________________________________________________________________ LICZBY ZMIENNOPRZECINKOWE TYPU float. To, �e C++ przy wywo�aniu funkcji jest "przyzwyczajony" do odk�adania argument�w na stos zawsze po dwa bajty mo�e nam sprawi� troch� k�opot�w, gdy zechcemy zastosowa� argument typu float, double, b�d� long - znacznie przekraczaj�cy d�ugo�ci� dwubajtowe s�owo maszynowe. # include <.... .... # pragma inline void main() { float liczba = 3.5; .... Je�eli zajrzymy do pami�ci naszego PC, oka�e si�, �e liczba 3.5 zosta�a tam "zaszyfrowana" jako 00 00 60 40. Dlaczego? Format liczb zmiennoprzecinkowych jest znacznie bardziej skomplikowany. Liczba dziesi�tna w rodzaju 123.4 to 1*102 + 2*101 + 3*100 + 4*10-1 {* !UWAGA SKLAD tu cyfry potegi wyzej *} Ale PC mo�e pos�ugiwa� si� wy��cznie zerami i jedynkami, i liczy� wy��cznie w systemie dw�jkowym. Liczb� dziesi�tn� 3.5 mo�naby przedstawi� dw�jkowo np. tak: 1*21 + 1*20 + 1*2-1 = 2 + 1 + 1/2 {* !UWAGA SKLAD: potegi *} czyli 0000 0000 0000 0011.1000 0000 0000 0000 Kropka oznacza przecinek oddzielaj�cy cz�� ca�kowit� od cz�ci u�amkowaj - "przecinek dw�jkowy" (a nie dziesi�tny!). Ka�d� liczb� dziesi�tna mo�na zamieni� na liczb� dw�jkow�. Przyk�adowodziei�tne 7.75 mo�na zamieni� na 4 + 2 + 1 + 1/2 + 1/4 = 0000 0000 0000 0111.1100 (dw�jkowo) Pozostaje jednak pewien problem. Komputer nie ma mo�liwo�ci zaznaczenia przecinka, dlatego te� przecinek musi by� ustawiany zawsze w tej samej pozycji - blisko pocz�tku liczby. Liczby zmiennoprzecinkowe s� poddawane "normalizacji" (ang. noralized). Nasza liczba 0000 0000 0000 0011.1000 po normalizacji b�dzie wygl�da� tak: 1.110 0000 0000... * 2^1. Odbywa si� to zupe�nie tak samo, jak normalizacja liczb dziesi�tnych. Przesuni�cie przecinka powoduje, �e 12345.67 = 1.234567 * 10^4. Aby wr�ci�a do swojej starej "zwyk�ej" postaci (jest to tzw. "rozwini�cie" liczby - ang. expand) nale�y przesun�� przecinek o jedno miejsce w prawo - otrzymamy znowu 11.1 . W liczbach dziesi�tnych pierwsza cyfra mo�e by� r�na (tylko nie zero), a w dowolnej poddanej normalizacji zmiennoprzecinkowej liczbie dw�jkowej pierwsz� cyfr� jest zawsze 1. Skoro w formacie liczb zmiennoprzecinkowych pierwsza jedynka jest przyjmowana "z definicji" (ang. implicit), wi�c mo�na j� pomin��. Zostanie nam zatem zamiast 1.11 tylko 11 i ta przechowywana cz�� liczby jest nazywana jej cz�ci� znacz�c� (ang. significant). To jeszcze nie wszystko - powinien tam by� wyk�adnik pot�gi. Wystarczy zapami�ta� wyk�adnik, bo podstawa jest zawsze ta sama - 2. Niestety wyk�adniki s� przechowywane nie w spos�b naturalny, a po dodaniu do nich tzw. przesuni�cia (ang. offset lub bias). Pozwala to unikn�� k�opot�w z okre�laniem znaku wyk�adnika pot�gi. Dla liczb typu float offset wyk�adnika wynosi +127 a dla liczb double float +1023. Wr�cmy do naszej przyk�adowej liczby. Je�li nasza liczba 3.5 = 11.1(B) ma by� zapisana w postaci zmiennoprzecinkowej - float, zapisany w pami�ci wyk�adnik pot�gi wyniesie: 1 + 127 = 128 = 80 (hex) A teraz znak liczby. Pierwszy bit ka�dej liczby zmiennoprzecinkowej okre�la znak liczby (ang. sign bit). Liczby zmiennoprzecinkowe nie s� przechowywane w postaci dw�jkowych uzupe�nie�. Je�li pierwszy bit - bit znaku r�wny jest 1 - liczba jest ujemna. natomiast je�eli 0, liczba jest dodatnia. Jest to jedyna r�nica pomi�dzy dodatnimi a ujemnymi liczbami zmiennoprzecinkowymi. Nasza liczba 3.5 = 11.1 zostanie zakodowana jako: znak liczby - 0 wyk�adnik pot�gi - 1000 0000 cyfry znacz�ce liczby - 110000000.... Poniewa� wiemy, �e mamy do dyspozycji dla liczb float 4 bajty (mo�esz to sprawdzi� sizeof(float x=3.5)), uzupe�nijmy brakuj�ce do 32 bity zerami: 3.5 = 0100 0000 0110 0000 0000 0000 0000 0000 = 40 60 00 00 zapis 40600000 to oczywi�cie szesnastkowa posta� naszej liczby. Je�li teraz we�miemy pod uwag�, �e nasz PC zamieni miejscami starsze s�owo z m�odszym 00 00 40 60 a nast�pnie w obr�bie ka�dego s�owa dodatkowo starszy bit z m�odszym, to zrozumiemy, dlaczego nasza liczba "siedzia�a" w pami�ci w zaszyfrowanej postaci 00 00 60 40. Rozpatrzmy szkielet programu wykorzystuj�cego funkcj� z "d�ugim" argumentem. Aby zapanowa� nad zapisem liczby zmiennoprzecinkowej do pami�ci naszego PC mo�emy na poziomie assemblera post�pi� np. tak: # include <..... # pragma inline void funkcja(long int) //Prototyp funkcji main() { long liczba = 0xABCDCDEF; //Deklaracja argumentu ..... funkcja(liczba); //Wywo�anie w programie .... } void funkcja(long int x) //Implementacja funkcji { ..... } // x - argument formalny Argument przekazywany funkcji() jest zmienn� 4 - bajtow� typu long int. Mo�emy j� zamieni� na dwa s�owa, zanim przeka�emy j� do wykorzystania w asemblerowskiej cz�ci programu. funkcja(long int x) { int x1starsze, x2mlodsze; //Wewn�trzne zmienne pomocnicze x2mlodsze = (int) x; x >> 16; x1starsze = (int) x; _DX = x1starsze; _BX = x2mlodsze; asm { ...... //Tu funkcja ju� mo�e dzia�a� Forsuj�c konwersj� typu na (int), spowodujemy, �e m�odsze s�owo zostanie przypisane zwyczajnej kr�tkiej zmiennej x2mlodsze. Nast�pnie zawarto�� d�ugiej zmiennej zostanie przesuni�ta o 16 bit�w w prawo (starsze s�owo zostanie przesuni�te na miejsce m�odszego). Powt�rzenie operacji przypisania spowoduje przypisanie zmiennej x1starsze starszej po��wki s�owa. Od tej chwili mo�emy odwo�a� si� do tych zmiennych w naszym fragmencie napisanym w asemblerze. Post�pujemy tak, by to C++ martwi� si� o szczeg�y techniczne i sam manipulowa� stosem i jednocze�nie pilnowa� poprawno�ci konwersji danych. ZWROT WARTO�CI PRZEZ FUNKCJ�. A teraz kilka s��w o tym, co si� dzieje, gdy funkcja zapragnie zwr�ci� jak�� warto�� do programu. Wykorzystanie przez funkcje rejestr�w do zwrotu warto�ci. ________________________________________________________________ Typ warto�ci Funkcja u�ywa rejestru (lub pary) ________________________________________________________________ signed char / unsigned char AL short AX int AX enum AX long para DX:AX (starsze s�owo DX, m�odsze AX) float AX = Adres (je�li far to DX:AX) double AX = Adres (je�li far to DX:AX) struct AX = Adres (je�li far to DX:AX) near pointer AX far pointer DX:AX ________________________________________________________________ Zale�nie od typu warto�ci zwracanej przez funkcj� (okre�lonej w prototypie funkcji), C++ odczytuje zawarto�� odpowiedniego rejestru: AL, AX lub DX:AX. Je�li funkcja ma np. zwr�ci� warto�� o d�ugo�ci jednego bajtu, to przed wyj�ciem z funkcji nale�y j� "zostwi�" w rejestrze AL. Je�li wywo�uj�c funkcj� C++ oczekuje zwrotu warto�ci jednobajtowej, to po powrocie z funkcji automatycznie pobierze bajt z rejestru AL. Kr�tkie warto�ci (typu short int) s� "pozostawiane" przez funkcj� w AX, a d�ugie w parze rejestr�w: DX - starsze, AX - m�odsze s�owo. Zastosujmy to w programie. Funkcja b�dzie odejmowa� dwie liczby ca�kowite. Pobierze dwa argumenty typu int, wykona odejmowanie i zwr�ci wynik typu int (return (_AX)). Dla modelu pami�ci small b�dzie to wygl�da� tak: [P064.CPP] # include <iostream.h> # pragma inline int funkcja(int, int); //Prototyp funkcji void main() { cout << "\nWynik 7 - 8 = " << funkcja(7, 8); } int funkcja(int x, int y) //Implementacja funkcji { asm { MOV AX, x SUB AX, y } return (_AX); //Zwr�� zawarto�� rejestru AX } Zwr�� uwag�, �e po return(_AX); stawiamy �rednik, natomiast po instrukcjach assemblera nie: asm MOV AX, DX chyba, �e chcemy umie�ci� kilka instrukcji assemblera w jednej linii (patrz ni�ej). C++ i assembler s� r�wnoprawnymi partnerami. C++ mo�e odwo�ywa� si� do zmiennych i funkcji assemblera, je�li zosta�y zadeklarowane, jako publiczne (public) oraz zewn�trzne (EXTeRNal) i vice versa. C++ oczekuje, �e zewn�trzne identyfikatory b�d� si� rozpoczyna� od znaku podkre�lenia "_". Je�li w programie pisanym w BORLAND C++ zastosujemy zewn�trzne zmienne i funkcje, C++ sam automatycznie doda do identyfikator�w znak podkre�lenia. Turbo Assembler nie robi tego automatycznie i musimy zadba� o to "r�cznie". Przyk�adowo, wsp�praca pomi�dzy programem P .CPP i modu�em MODUL.ASM b�dzie przebiega� poprawnie: [P065.CPP] extern int UstawFlage(void); //Prototyp funkcji int Flaga; void main() { UstawFlage(); } [MODUL.ASM] .MODEL SMALL .DATA EXTRN _Flaga:WORD .CODE PUBLIC _UstawFlage _UstawFlage PROC CMP [_Flaga], 0 JNZ SKASUJ_Flage MOV [_Flaga], 1 JMP SHORT KONIEC SKASUJ_Flage: MOV [_Flaga], 0 KONIEC: RET _UstawFlage ENDP ...
jacek_040