Wywoływanie funkcji przez konwersję typów
Język programowania Pascal ma specyficzny typ zmiennych tzw. proceduralny. W dużym uproszczeniu polega on na tym, że odwołanie do zmiennej ma podobne konsekwencje, jak wywołanie procedury. Kompilator języka C nie oferuje programiście podobnego typu zmiennych, jednak realizacja tak pojętego typu proceduralnego jest możliwa dzięki wykorzystaniu wskaźników oraz mechanizmów przekształceń typów. Na listingu 1 umieszczono fragment programu z definicjami odpowiednich typów zmiennych. Jako pierwszy zdefiniowano typ zmiennej będącej wskaźnikiem typu char, kwalifikowanym do przestrzeni adresowej zawierającej kod programu mikrokontrolera (code). Za wskaźnikiem jest lista argumentów, która w tym przypadku jest pusta. Następnie ten typ wykorzystywany jest do budowy tablicy zawierającej wykaz funkcji. Tablica również zakwalifikowana została do obszaru code, ponieważ zawiera wartości stałe, nieulegające zmianie w czasie wykonywania programu. Rozmiar tablicy – wykazu funkcji – nie jest ustalony. Jej koniec sygnalizuje znak o kodzie „0”. Dla przykładu i dla uproszczenia funkcja command pokazana na list. 1, zwraca wartość umieszczoną w tablicy pod indeksem 0. Zgodnie z wcześniejszą definicją, odpowiada to wskazaniu adresu, pod którym umieszczona jest funkcja o nazwie test_1. Funkcja zawarta w definicji struktury musi zwracać jakąś wartość. Najłatwiej, gdy jest to wartość typu char, będąca np. kodem błędu, jednak może to być również inny typ zmiennych. Jest to wymóg konieczny dla późniejszej realizacji polecenia return(wykaz[indeks].funkcja()), ponieważ polecenie return nie może zwracać wartości typu void oraz z powodów, o których będzie mowa dalej. Funkcja command powinna być tego samego typu, jak określono to w definicji struktury. Powinna, ale nie musi. Jeśli typy będą różne, to nastąpi niejawna konwersja typu zwracanej wartości. Na listingu 2 pokazano fragment programu odpowiadający funkcji command po kompilacji do postaci języka asembler. Kompilator wykonując konwersję wskaźnika do postaci char wywołuje ukrywającą się pod wskazaniem funkcję. Jest to konieczne aby funkcja zwróciła wartość, która będzie mogła ulec zamianie na typ char. Na listingu 3 pokazano umieszczono przykład praktycznego wykorzystania omówionego wyżej mechanizmu. Są to ważniejsze fragmenty programu realizującego funkcję interpretera następujących poleceń:
- IN <numer portu> np. IN 1 - odczyt portu o podanym numerze
- OUT <numer portu> <wartość> np. OUT 1 0x20 - zapisuje do portu o podanym numerze liczbę,
- STATUS – podaje informację o statusie WYŁĄCZONY/AKTYWNY,
- ON - załączenie operacji na portach tj. poleceń IN i OUT,
- OFF - wyłączenie operacji na portach tj. blokowanie funkcjonowania poleceń IN i OUT,
- HELP lub ? - informacja o realizowanych poleceniach.
Definicja struktury o nazwie komendy zawiera więcej składników niż we wcześniejszym przykładzie. Obok wskaźnika do funkcji pojawił się również wskaźnik do łańcucha znaków umieszczonego w pamięci programu mikrokontrolera. Jest to nazwa, po której rozpoznawane są polecenia i jednocześnie najprostsza z metod powiązania nazwy symbolicznej z odpowiadającą mu funkcją. Dla łatwiejszej analizy przykładu nazwy funkcji są niemal identyczne z odpowiadającymi im poleceniami.
Listing 1. Fragment programu z definicjami zmiennych - wskaźników do funkcji
//struktura przeznaczona na umieszczanie definicji komend
typedef struct
{
char (code *funkcja)(void);
}komendy;
//tablica z wykazem komend
code komendy wykaz[] =
{
test_1,
test_2,
0
};
char command()
{
return(wykaz[0].funkcja());
}
Listing 2. Polecenie return(wykaz[0].funkcja()) po skompilowaniu (Intel 8051, kompilator Raisonance RC-51).
0000 900000 R MOV DPTR,#wykaz ;do rejestru DPTR młodszy bajt adresu funkcji z tablicy wykaz
0003 7400 MOV A,#00 ;A jako wartość indeksu do tablicy – tu 0
0005 93 MOVC A,@A+DPTR ;załaduj do A bajt spod adresu wykaz+0
0006 FA MOV R2,A ;przechowaj w rejestrze R2
0007 900000 R MOV DPTR,#wykaz ;ponownie do DPTR adres tablicy wykaz
000A 7401 MOV A,#1 ;ale indeks tablicy w tym przypadku to 1
000C 93 MOVC A,@A+DPTR ;do A starszy bajt adresu funkcji z tablicy wykaz+1
000D FB MOV R3,A ;przechowaj w rejestrze R3
000E 8A83 MOV DPH,R2 ;przepisz R2 do DPH
0010 8B82 MOV DPL,R3 ;przepisz R3 do DPL
0012 120000 R LCALL ?C_INDCALL ;wywołaj wewnętrzną procedurę RC-51 uruchamiającą
;funkcję spod adresu wskazywanego przez DPTR
0015 22 RET ;powrót do programu głównego
Listing 3. Ważniejsze fragmenty interpretera poleceń
//definicje nagłówków funkcji programu
char in(char data *bufor);
char out(char data *bufor);
char status(char data *bufor);
char on(char data *bufor);
char off(char data *bufor);
char help(char data *bufor);
//definicja typu dla tablicy - wykazu poleceń
typedef struct
{
char code *komenda;
char (code *funkcja)(char data *);
}komendy;
//tablica z wykazem komend i powiązanych z nimi funkcji
code komendy wykaz[] =
{
"IN", in,
"OUT", out,
"STATUS", status,
"ON", on,
"OFF", off,
"HELP", help,
"?", help,
"", NULL //koniec wykazu
};
//wyszukiwanie komend oraz wywołanie odpowiadających im funkcji
char command(char data *bufor)
{
char i, j; //256 komend x 256 znaków
for (i = 0;;)
for (j = 0;; )
{
if(wykaz[i].komenda[j] != 0) //jeśli jest komenda
{ //zamiana małych liter na duże
if(((wykaz[i].komenda[j]^bufor[j]) & 0x5F) == 0)
{
j++;
continue; //następny znak
}
i++;
break; //następna komenda
}
if( j == 0 ) //brak komendy w wykazie
{
printf("%s\n","BLAD: Nie rozpoznano komendy!");
return(0);
}
else return(wykaz[i].funkcja(bufor+j)); //wykonanie funkcji
}
}
Dodaj nowy komentarz