LEKCJA17.TXT

(21 KB) Pobierz
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  
    ...
Zgłoś jeśli naruszono regulamin