środa, 27 maja 2009

Wywołanie funkcji pow w asemblerze (crt_pow)

Dzisiaj natknąłem się na niemały problem.. jak można potęgować dwie liczby zmiennoprzecinkowe w asemblerze. Poszukiwania zacząłem od plików nagłówkowych masma, ale niestety jedyny mój trop właściwie prowadził tylko do funkcji crt_pow. Ale niestety, biblioteki masma są wręcz beznadziejnie opisane (w przeciwieństwie do MSDN :P) więc zajęło mi trochę czasu, by cokolwiek znaleźć przez google.. ale nic nie wygooglowałem. Także trzeba było znaleźć rozwiązanie metodą prób i błędów. Rozwiązanie okazało się dosyć proste ;)

Najpierw musimy dołączyć do naszego programu nagłówek msvcrt.inc oraz bibliotekę msvcrt.lib. Następnie w odpowiednim miejscu programu wpisujemy:
LOCAL tmp:DWORD ; potrzebujemy zmiennej pomocniczej
...
finit ; inicjujemy koprocesor
mov tmp, 2 ; pobieramy liczbę 2
fild tmp ; wrzucamy ją na stosu koprocesora
mov tmp, 5 ; pobieramy liczbę 5
fild tmp ; wrzucamy ją na stos koprocesora
; teraz mamy st0 = 5, st1 = 2
sub esp, 16 ; inicjujemy 16 bajtów miejsca na stosie programu
fstp qword ptr[esp] ; pierwszą liczbą umieszczoną na stosie jest 5
fstp qword ptr[esp+8] ; drugą - 2
call crt_pow ; obliczamy 5^2
add esp, 16 ; "zdejmujemy" dwie liczby ze stosu
; z otrzymaną wartością można zrobić cokolwiek ;)


Wynik potęgowania pozostaje w st0, czyli na szczycie stosu koprocesora. Jak widać, nie jest to aż tak trudne zadanie jak się może wydawać ;) Ważne jest, by pamiętać o odpowiednich zmianach wartości wskaźnika stosu.

Mam nadzieję, że choć komuś to pomogło, w szczególności osobom, które nie mają zbyt wiele doświadczenia w asemblerze.

środa, 26 marca 2008

Wskaźniki na... "coś"

Zdarza się, że potrzebujemy w jednym obiekcie przechować dane więcej niż tylko jednego konkretnego typu. Np. chcielibyśmy otrzymać stos czy jakąś listę, na której znalazłyby się liczby zmiennoprzecinkowe, stałoprzecinkowe, łańcuchy znaków albo nawet obiekty dowolnej klasy. I co wtedy można zrobić? Teoretycznie można by wykorzystać unię - ale jest to dobre tylko dla niewielkich obiektów. Lepszym wyjściem byłoby użycie jakiegoś wskaźnika (przecież wskaźnik na dowolny typ na jednej platformie zajmuje zawsze tyle samo miejsca, dla 32-bit: 4B). No to świetnie, idea już jest, ale zaraz.. jak takie coś można właściwie zrobić? Jaki typ ma być tego wskaźnika?

Wskaźnik musi być zadeklarowany tak:
void* Wartosc;

No świetnie, ale jeszcze chyba jednego brakuje... przecież tak naprawdę nie wiemy, co (jaki typ) będzie wskazywane przez ten nasz nowy wskaźnik... może tam być dosłownie wszystko. Dlatego rozszerzymy sobie tą deklarację na klasę:

class TElement {
public:
void* Wartosc;
short int Typ;
};


Typ będzie nam potrzebny do tego, by potem uzyskiwać odpowiednie dane.
Na koniec musimy jeszcze oczywiście wprowadzić dane:

int* Calkowita;
double* ZmiennoPrzecinkowa;
TElement Tablica[2];

Calkowita = new int;
*Calkowita = 5;
Tablica[0].Wartosc = Calkowita;
Tablica[0].Typ = 1; // int
ZmiennoPrzecinkowa = new double;
*ZmiennoPrzecinkowa = 3.445;
Tablica[1].Wartosc = ZmiennoPrzecinkowa;
Tablica[1].Typ = 2; // double


Oczywiście, nie byłoby to nam do niczego potrzebne, gdybyśmy jeszcze nie wiedzieli, jak potem to wykorzystać. A i to wcale nie jest znowu takie trudne; ważne jest rzutowanie naszego wskaźnika na odpowiedni wskaźnik... Może przykład to rozjaśni:

switch (Tablica[0].Typ) {
case 1:
Calkowita = static_cast<int*> (Tablica[0].Wartosc);
cout << *Calkowita;
break;
case 2:
ZmiennoPrzecinkowa = static_cast<double*> (Tablica[0].Wartosc);
cout << *ZmiennoPrzecinkowa;
break;
}


Prawda, że proste?
Naturalnie, powyższą strukturę można wykorzystać dla dowolnych typów danych, włączając w to pojemniki STL'a, klasy, wskaźniki na obiekty, itd... Ile typów, tyle możliwości w skrócie.

sobota, 15 marca 2008

Wskaźniki na funkcje

Zdarza się czasem, że potrzebujemy wykorzystać kilka funkcji w naszym programie. Jednym ze sposobów jest zaimplementowanie wielu różnych funkcji przy pomocy instrukcji sterujących (if, switch). Jednak wtedy rozmiar kodu może się drastycznie zwiększyć, tudzież czytelność kodu może zmaleć. Co wtedy robić? Z pomocą przychodzą wskaźniki. Z początku bardzo nie lubiłem wskaźników w C++, jednak pomału przyzwyczajam się do nich, mało tego, uważam, że są bardzo całkiem proste w użyciu! Tak, tak, dobrze przeczytałeś. Nie są trudne, ważne, by załapać ideę wskaźników, a potem to już pójdzie po maśle ;)

No ale wróćmy do tematu. Wskaźnik na funkcję. Jak można się domyślić, jest to po prostu liczba, która wskazuje adres w pamięci, gdzie zaczyna się nasza szukana funkcja. No dobrze, ale jak to wykorzystać? Są dwa sposoby. Pierwszy polega na napisaniu dodatkowej funkcji. Jeśli nasza funkcja posiada jeden argument (zakładamy, że argumenty i wartości zwracane przez funkcję są typu double - naturalnie, może to być dowolny typ):

double funkcja(double (*wskaznik_na_funkcje)(double), double x)
{
double wynik;
wynik = (*wskaznik_na_funkcje) (x);
return (wynik);
}


Początek deklaracji nie powinien dziwić - typ zwracany przez naszą funkcję pomocniczą i jej nazwa. Pierwszym parametrem jest wskaźnik na naszą funkcję, wraz z listą parametrów, jakie potrzebuje ta funkcja (w naszym wypadku jest to jeden parametr). Pierwsze double to oczywiście wartość zwracana przez funkcję. Drugim parametrem jest wartość liczbowa, która będzie potem parametrem wskazywanej funkcji.
W ciele funkcji deklarujemy sobie dodatkową zmienną, w której przechowamy wynik. I najważniejsze w tym wszystkim: wynik = (*wskaznik_na_funkcje) (x);. Te nawiasy są jak najbardziej potrzebne, w przeciwnym wypadku kompilator będzie to interpretować jako wskaźnik na liczbę. I teraz najważniejsze - wykorzystanie:

double a, b;
a = funkcja(sin, 1.74);
b = funkcja(cos, 1.74);
cout << a << "\n";
cout << b << "\n";



Innym sposobem jest wykorzystanie bezpośrednie tego wskaźnika, a mianowicie: deklarujemy najpierw zmienną wskaźnikową wraz z parametrami:

double (*func)(double);

A następnie musimy przypisać temu wskaźnikowi adres naszej funkcji:

func = sin;
a = func(1.74);
func = cos;
b = func(1.74);


Wykorzystanie, jak widać, jest również proste.


Można także skorzystać z tablicy wskaźników na funkcje.
Deklaracja tablicy wygląda tak:

double (*funkcje[4])(double);

Chyba nie trzeba tego tłumaczyć... Następnie przydzielamy odpowiednie wartości, np:

funkcje[0] = sin;
funkcje[1] = cos;
funkcje[2] = tan;
funkcje[3] = ctg;


(funkcję ctg zdefiniowałem jako: pow(tan(x), -1.0))

I na koniec wykorzystanie:

for (int i = 0; i < 4; i ++)
cout << funkcje[i](3.1415 / 4.0) << "\n";


Prawda, że ładniej to wygląda? ;)