Etapy kompilacji Przetwarzanie wstępne — preprocessing
Transkrypt
Etapy kompilacji Przetwarzanie wstępne — preprocessing
Etapy kompilacji #define ILE 100 ... for (i=0; i<ILE; i++) ... ude przetwa- #incl rzanie #defin e wstępne #if ❄ ... for (i=0; i<100; i++) ... Przetwarzanie wstępne — preprocessing ......................kompilacja .............................. .............. właściwa ................. .... ... ... ...❄ ✶ binaria dane q wyniki Wykład 7. Przetwarzanie wstępne, str. 2 Przetwarzanie wstępne — preprocessing • Przed właściwą kompilacją tekst programu jest przekształcany zgodnie z dyrektywami programisty. • To przekształcenie jest czysto tekstowe — preprocesor nie zajmuje się logiką programu. Nieostrożnie napisane dyrektywy mogą spowodować, że program nie da się skompilować, albo będzie działał błędnie. • Podstawowe dyrektywy: – #include — włączyć plik – #define — zastąpić w tekście programu – #if — kompilować warunkowo Przetwarzanie wstępne — preprocessing ... ... ... ... ... ... ... ... .. .. ... . .. .. .. . . .. .. .. .. . .. .. .. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... .. ... ... . .. .. .. . . .. .. .. .. . .. .. .. .... ... ... ... .. ... ... ... ... ... . ... ... ... #include "plik" ... ... ... pl ik #include — program na wielu plikach Włącza (bez interpretowania) cały plik. Dyrektywa #include"plik" włącza plik z aktualnego katalogu — opracowany przez programistę. Dyrektywa #include<plik> włącza plik z katalogu kompilatora — opracowany przez autora kompilatora. Wykład 7. Przetwarzanie wstępne, str. 4 Przetwarzanie wstępne — preprocessing #include — program na wielu plikach #include "globalne" int main ( ) { printf("\n %s\n\n", haslo); return 0; } globalne #include<stdio.h> char haslo[20] = "Naprzod!"; Drukuje: Naprzod! Plik „inkludowany” może „inkludować” inne pliki. Przetwarzanie wstępne — preprocessing #include — program na wielu plikach Co inkludujemy, pisząc na początku programu #include<stdio.h> lub #include<math.h> itp.? (Na sigmie te pliki mieszczą w /usr/include/ — można zajrzeć.) W standardowym pliku nagłówkowym (header file) mieszczą się m.in. • definicje typów — np. FILE • definicje stałych — np. EOF • deklaracje zmiennych — np. stdout (zmienna typu FILE*, wskaźnik na plik związany z ekranem) • nagłówki funkcji — np. printf; wyłącznie nagłówki, bez definicji! Np. int printf(char* format, ...); Definicje funkcji standardowych mieszczą się w bibliotece standardowej C, a nie w pliku nagłówkowym. Wykład 7. Przetwarzanie wstępne, str. 6 Przetwarzanie wstępne — preprocessing #include — własny plik nagłówkowy Modularność programu ułatwia panowanie nad dużymi systemami. W najprostszym przypadku: • funkcje ogólne na jednym pliku; np. 1.ulamki-definicje.c • używający ich program główny na innym; np. 1.ulamki-glowny.c • wspólna kompilacja: gcc -Wall 1.ulamki-definicje.c 1.ulamki-glowny.c (patrz 07-Ulamki/ ). gcc -Wall część wspólna część wspólna definicje główny Przetwarzanie wstępne — preprocessing #include — własny plik nagłówkowy część wspólna gcc -Wall • • • • #include .. .h .................... ................................. .......... ............. . . . . . . . . ....... ...... . . ..... . . .. ... .... ... ... ... . ❄ . " " #include " ..❄ " definicje główny typy, stałe, zmienne i nagłówki funkcji; np. 2.ulamki.h ; funkcje ogólne; np. 2.ulamki-definicje.c ; używający ich program główny; np. 2.ulamki-glowny.c ; wspólna kompilacja. ) biblioteka Wykład 7. Przetwarzanie wstępne, str. 8 Przetwarzanie wstępne — preprocessing #include — własny plik nagłówkowy • Utrzymywanie tej samej części wspólnej w kilku różnych plikach jest niepraktyczne. Dokonywanie w niej zmian wymaga zmieniania kilku plików. • Staramy się, żeby ten sam kod pisany był tylko raz; albo w pliku z definicjami, albo w programie głównym. • Minimum tego, co musi być znane zarówno definicjom jak programowi głównemu (definicje typów, stałych, deklaracje zmiennych, nagłówki funkcji) wynosimy do odrębnego pliku nagłówkowego. • Nazwy plików nagłówkowych tradycyjnie mają rozszerzenie .h od ang. header . • Autor programu głównego nie musi znać treści pliku z definicjami, ale powinien znać treść pliku nagłówkowego. Przetwarzanie wstępne — preprocessing #define — zastępowanie w programie ... ... ... ... ... ... ... ... .. .. ... . . .. .. .. . . .. .. .. . .. .. .. ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... .. .. ... . . .. .. .. . . .. .. .. . .. .. .. ... ... .. .... ... ... ... ... ... ... ... #define ABC defg ... ABC ... ABC ... ... ... ... ... ... ... ... ... .. .. ... . . .. .. .. . . .. .. .. . .. .. .. ... ... ... ... ... ... ... ... ... ... ... ... ... defg ... defg ... ... ... ... ... ... ... ... ... .. .. ... .. .. .. .. . . .. .. .. . .. .. .. ... ... ... ... ... ... ... ... ... ... Dyrektywa #define MAKRO reszta wiersza powoduje zastąpienie wszystkich wystąpień MAKRa w programie przez resztę wiersza. Wykład 7. Przetwarzanie wstępne, str. 10 Przetwarzanie wstępne — preprocessing #define — zastępowanie w programie Przykład: (UWAŻAĆ!) M #define ILE 100; ··· if (ILE < 1000) printf("\n mniejsze\n\n"); else printf("\n niemniejsze\n\n"); sygnalizuje błąd: error: expected ’)’ before ’;’ token Wynik zastąpienia: if (100; < 1000) printf("\n mniejsze\n\n"); else printf("\n niemniejsze\n\n"); Winny jest średnik. Przetwarzanie wstępne — preprocessing #define — zastępowanie w programie • Nie zastępuje się części słów; np. nie będzie żadnego zastąpienia w tym przypadku: #define KOT pies ............... ... BOJKOT ... • Nie zastępuje się we wnętrzu cudzysłowu; np. nie będzie żadnego zastąpienia w tym przypadku: #define KOT pies ............... printf("Wlazł KOT na płot.\n"); Wykład 7. Przetwarzanie wstępne, str. 12 Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami Makro może mieć parametry — nazwy pooddzielane przecinkami. Przy zastępowaniu parametry są tekstowo (czyli na ślepo, „bezmyślnie”) zastępowane argumentami wywołania. #define KWADRAT(x) x*x ........................ printf(" %i\n", KWADRAT(4)); printf(" %.2lf\n", KWADRAT(1.2)); printf(" %i\n", 4*4); printf(" %.2lf\n", 1.2*1.2); Ale trzeba uważać. . . Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami #define KWADRAT(x) x*x ........................ printf(" %i\n", KWADRAT(2+2)); Taka komenda drukuje 8 (zamiast 16 ). Wynik zastąpienia: 2+2*2+2 Lepsza definicja: #define KWADRAT(x) (x)*(x) wynik zastąpienia: (2+2)*(2+2) Wykład 7. Przetwarzanie wstępne, str. 14 Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami #define DLA(I, N) for(I=0; I<(N); I++) — pętla po I od 0 do N Wtedy: w=1; w=1; DLA(i,N) w = 2*w; DLA(i,N) w = w*(i+1); DLA(i,n) DLA(j,k) tab[i][j] = 0; — oblicza potęgę w = 2N — oblicza silnię w = N ! — zeruje tablicę tab[n][k] Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami UWAGA NA SPACJE! #define ZLE (int)0.6+(int) ..................... printf("%i\n", (int)(ZLE(0.6))); #define ZLE(int)0.6+(int) ..................... printf("%i\n", (int)(ZLE(0.6))); makro bez parametrów: printf("%i\n", (int)((int)0.6+(int)(0.6))); drukuje: 0 makro z parametrem: printf("%i\n", (int)(0.6+0.6)); drukuje: 1 Wykład 7. Przetwarzanie wstępne, str. 16 Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami Poprzedzenie parametru makra płotkiem # powoduje ujęcie argumentu w cudzysłów: #define PYTAJ O(zmienna) \ { printf(" %s == ", #zmienna); scanf("%i", &zmienna); } Wtedy PYTAJ O(N) rozwija się do { printf(" %s == ", "N"); scanf("%i", &N); } i drukuje N == a następnie wczytuje liczbę całkowitą do zmiennej N . Przetwarzanie wstępne — preprocessing #define — makra standardowe Komenda: printf(" Data: %s. Czas: %s. Plik: %s. Wiersz: %i.\n", DATE , TIME , FILE , LINE ); wydrukowała: Data: Mar 31 2013. Czas: 14:43:33. Plik: test.c. Wiersz: 5. DATE TIME FILE LINE — — — — data czas (godz.:min.:sek.) przetwarzany plik nr wiersza Wykład 7. Przetwarzanie wstępne, str. 18 Przetwarzanie wstępne — preprocessing #define — zastosowanie makr FILE i #define BLAD WEWN\ printf("\n BLAD WEWNETRZNY.");\ printf("\n Powiadom autora programu podajac:");\ printf("\n nazwe pliku: %s", FILE );\ printf("\n numer wiersza:%i\n\n", LINE ); ..................... if (...) BLAD WEWN ..................... drukuje: BLAD WEWNETRZNY. Powiadom autora programu podajac: nazwe pliku: test.c numer wiersza: 10 LINE Przetwarzanie wstępne — preprocessing #define — zastępowanie z parametrami Napisy, ujęte w cudzysłowy i postawione obok siebie, zlewają się w jeden: char napis[30] = "Maly " "dom."; = char napis[30] ="Maly dom."; #define OSTRZEGAJ JESLI(cos zle) \ if (cos zle)\ printf ("OSTRZEZENIE: " #cos zle " w wierszu %i\n", .............................. OSTRZEGAJ JESLI(a[i]==0) LINE ); if (a[i]==0) printf ("OSTRZEZENIE: " "a[i]==0" " w wierszu %i\n", 10); drukuje: OSTRZEZENIE: a[i]==0 w wierszu 10 Wykład 7. Przetwarzanie wstępne, str. 20 Przetwarzanie wstępne — preprocessing Dziedziczenie wielostronne W wyniku wielostronnego dziedziczenia przez #include dojść może do wielokrotnej definicji tego samego typu. Błąd: conflicting types for ’punkt’ Jak tego uniknąć? plik punkt.h : typedef struct { double x,y; } punkt; plik odcinek.h : plik trojkat.h : #include "punkt.h" typedef struct { punkt pocz,kon; } odcinek; #include "punkt.h" typedef struct { punkt w1,w2,w3; } trojkat; plik geom.h : #include "odcinek.h" #include "trojkat.h" ...... trojkat tr = {{0,0}, {1,0}, {0,1}}; ...... Przetwarzanie wstępne — preprocessing Dziedziczenie wielostronne W wyniku wielostronnego dziedziczenia przez #include dojść może do wielokrotnej definicji tego samego typu. Błąd: conflicting types for ’punkt’ Jak tego uniknąć? plik punkt.h : #ifndef PUNKT #define PUNKT typedef struct { double x,y; } punkt; #endif #define PUNKT definiuje makro specyficzne dla pliku punkt.h . Część programu między #ifndef PUNKT a #endif jest wykonywana tylko wtedy, gdy makro PUNKT jest niezdefiniowane. To się może zdarzyć tylko jeden raz. Wobec tego typ punkt zostanie zdefiniowany tylko raz. Wykład 7. Przetwarzanie wstępne, str. 22 Przetwarzanie wstępne — preprocessing Kompilacja warunkowa — dyrektywy #if, #else, #elif, #endif poprzednio omawiana konstrukcja #ifndef...#endif też jest przykładem kompilacji warunkowej Te dyrektywy włączają do kompilacji fragmenty programu np. w zależności od • używanej maszyny lub systemu operacyjnego lub kompilatora, • stadium uruchamiania programu — wydruki testowe, ostatecznie usuwane, • chwilowego wyboru wersji, • itp. Przetwarzanie wstępne — preprocessing Kompilacja warunkowa — dyrektywy #if, #else, #elif, #endif #if warunek0 program0 #elif warunek1 program1 #elif warunek2 program2 ........................ #elif warunekn programn #else programn+1 #endif jeśli spełniony jest warunek0 , to kompilowany jest tylko program0 jeśli spełniony jest warunek1 , to kompilowany jest tylko program1 . . . itd.. . . jeśli nie jest spełniony żaden z warunków, to kompilowany jest tylko programn+1 Niekompilowanie niepotrzebnego kodu daje oszczędność pamięci. Wykład 7. Przetwarzanie wstępne, str. 24 Przetwarzanie wstępne — preprocessing Kompilacja warunkowa — dyrektywy #if, #else, #elif, #endif Przykład: M (Patrz 07-Sortowanie/) W pliku sort.h można wybrać jeden z algorytmów: #define BUBBLE // #define INSERTION // #define MERGE W pliku sort.c temu wyborowi odpowiada kompilacja warunkowa: #if defined BUBBLE void sort(double tab[WLK TABLICY]) { . . . sortowanie bąbelkowe. . . } #elif defined INSERTION void sort(double tab[WLK TABLICY]) { . . . sortowanie przez wstawianie. . . } #elif defined MERGE void sort(double tab[WLK TABLICY]) { . . . sortowanie przez scalanie. . . } #else #error "Nieznany algorytm sortowania." #endif Przetwarzanie wstępne — preprocessing Jak automatycznie uzyskać wynik preprocessingu? Komenda gcc -E -CC plik z dyrektywami dokonuje przetwarzania wstępnego i niczego więcej; i drukuje tekst z rozwiniętymi dyrektywami: #define #define #define #define mleka piwa tego ten szczescie zly los czeka wykiwa Kto wypije mleka, tego szczescie czeka. Kto wypije piwa, ten zly los wykiwa.