Etapy procesu kompilacji.
Kod źródłowy (file.c) | | przetwarzanie wstępne (preprocesing) [preprocesor] | Kod wstępnie przetworzony (file.cpp) | | kompilacja (compilation) | Kod asemblera (file.s) | | asemblacja (assembly) | Kod obiektowy (file.o) | | linkowanie (linking) [program scalający (linker)] | dodanie kodu bibliotecznego i kodu startowego | Plik wykonywalny (file|file.out|file.exe)
Dyrektywy preprocesora.
#define NAZWA abc #undef NAZWA #define NAZWA(parametry) zawartość /* makro */ #define SKLEJAJ(lewy, prawy) lewy ## prawy /* SKLEJAJ(nazwa, 2) utworzy słowo nazwa2 */ #include <plik.h> #include "plik.h" #if (A==B) /* wiersz z if */ ... #elif (A==C) /* opcjonalne */ ... #else /* opcjonalne */ ... #endif /* Inne wiersze z if: #ifdef NAZWA #ifndef NAZWA */ #line stała "nazwa_pliku" #line stała #error ciąg_leksemów /* wypisanie komunikatu diagnostycznego */ #pragma ciąg_leksemów # /* pusta instrukcja preprocesora */ Symbole predefiniowane preprocesora: __FILE__ nazwa kompilowanego pliku __LINE__ numer kolejny bieżącego wiersza w pliku __DATE__ data kompilacji pliku __TIME__ czas kompilacji pliku __STDC__ sprawdzanie zgodności ze standardem ANSI C W systemie UNIX można definiować symbole preprocesora w wierszu poleceń podczas wywoływania polecenia inicjującego kompilację. Funkcja ta jest użyteczna w połączeniu z kompilacją warunkową. -Dnazwa -Dnazwa=zawartość -Unazwa (usuwanie symbolu)
/* Postać pliku nagłówkowego zawierająca zabezpieczenie przed wielokrotnym wstawianiem. */ #ifndef PLIK_H #define PLIK_H /* właściwa treść pliku plik.h */ #endif
W katalogu domowym utworzyć podkatalog auto. Utworzyć w nim pliki Makefile i auto.c postaci:
/* * auto.c * * Program wykorzystujacy kompilacje warunkowa. */ #include <stdio.h> /* Dla wygody definiujemy symbole */ #define AUTO_FIAT 1 #define AUTO_FORD 2 #define AUTO_OPEL 3 #define AUTO_BMW 4 /* Tutaj definiujemy nasz obecny przypadek */ #define AUTO_MOJE AUTO_BMW int main(void) { #if (AUTO_MOJE == AUTO_FIAT) printf("Najlepsze auto to Fiat\n"); #elif (AUTO_MOJE == AUTO_FORD) printf("Najlepsze auto to Ford\n"); #elif (AUTO_MOJE == AUTO_OPEL) printf("Najlepsze auto to Opel\n"); #elif (AUTO_MOJE == AUTO_BMW) printf("Najlepsze auto to BMW\n"); #else printf("To nie powinno sie zdarzyc\n"); #endif /* symbol AUTO_MOJE - przydatny komentarz */ return 0; }
Przeprowadzić kompilację etapami oglądając pliki pośrednie:
W katalogu makra stworzyć program wykorzystujący przykładowe makra parametryzowane. Makra są beztypowe, czyli działają na argumentach dowolnego typu, dla którego dozwolone są zastosowane operatory.
#define abs(x) ((x) >= 0 ? (x) : -(x)) #define max(a,b) ((a) < (b) ? (b) : (a)) #define min(a,b) ((a) < (b) ? (a) : (b)) #define kwadrat(a) ((a) * (a))
Warto przypomnieć w tym miejscu funkcje standardowe
#include <stdlib.h> int abs(int n) long labs(long n) #include <math.h> double fabs(double x)
Zapoznać się z programami z książki S. Oualline: VARS (zmienne stałe i tymczasowe), TRI-SUB (funkcja), LEN (znaleźć błąd), LEN2 (continue); użycie preprocesora - INIT2A, INIT2B (#define), BIG (znaleźć błąd), FIRST (znaleźć błąd), MAX (znaleźć błąd), SIZE (znaleźć błąd), DIE (znaleźć błąd), SQR (makro, znaleźć błąd), SQR-I (makro, znaleźć błąd), REC (makro, znaleźć błąd); użycie wskaźników - THING, CALL, ARRAY-P (wyświetlanie wskaźników), PTR2, PTR3, INIT-A (tablice i funkcje).
W katalogu domowym utworzyć podkatalog pelikan. Utworzyć w nim pliki Makefile i pelikan.c postaci:
/* * pelikan.c * * Program korzystajacy z parametrow wywolania. */ #define DEBUG #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("Wydruk parametrow wywolania:\n"); for (i = 0; i < argc; ++i) printf("Parametr nr %d to string: %s\n", i, argv[i]); if (argv[argc] == NULL) printf("To prawdza ze argv[argc] == NULL\n"); #ifdef DEBUG printf("Pracujemy w trybie DEBUG...\n"); #endif /* DEBUG */ return 0; }
Skompilować program, a następnie uruchomić poleceniem: ./pelikan a b c
Sprawdzić zachowanie programu po wykomentowaniu definicji symbolu DEBUG.
W katalogu domowym utworzyć podkatalog dzialania. Utworzyć w nim pliki Makefile i dzialania.c postaci:
/* * dzialania.c * * Program wykonujacy proste obliczenia w linii komend. */ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char *argv[]) { double x, y; if (argc < 3) { printf("Syntax: ./[ dodaj | pomnoz ] liczba1 liczba2\n"); } else if (strcmp(argv[0],"./dodaj") == 0) { x = atof(argv[1]); y = atof(argv[2]); printf("%f + %f = %f\n", x, y, x+y); } else if (strcmp(argv[0],"./pomnoz") == 0) { x = atof(argv[1]); y = atof(argv[2]); printf("%f * %f = %f\n", x, y, x*y); } else { printf("Dzialanie nieokreslone\n"); } return 0; }
Skompilować program do pliku dzialania, a następnie utworzyć linki symboliczne poleceniami:
ln -s dzialania dodaj ln -s dzialania pomnoz
Uruchomić program na kilka sposobów:
./dzialania ./dzialania 3 5 ./dodaj 4 3 ./pomnoz 2 5
Poprawić program, aby obsługiwał dzielenie, procenty, itp.
W katalogu domowym utworzyć podkatalog fabryka. Utworzyć w nim pliki Makefile i fabryka.c postaci:
/* * fabryka.c * * Program korzystajacy z opcji i argumentow. */ #include <stdio.h> #include <stdlib.h> #define TRUE 1 #define FALSE 0 int verbose = FALSE; /* tryb pelnej informacji */ char *infile = "data.in"; /* nazwa pliku wejsciowego domyslnego */ char *outfile = "data.out"; /* nazwa pliku wyjsciowego domyslnego */ char *program_name; /* nazwa programu */ void przetwarzaj(char *filename) { printf("Verbose: %s\nPlik wejsciowy: %s\nPlik wyjsciowy: %s\n", (verbose == TRUE ? "TRUE" : "FALSE"), filename, outfile); } void usage(void) { fprintf(stderr, "Syntax: %s [opcje] [lista plikow]\n",program_name); fprintf(stderr, "Opcje\n"); fprintf(stderr, " -h pomoc (help)\n"); fprintf(stderr, " -v tryb pelnej informacji (verbose)\n"); fprintf(stderr, " -o<nazwa> nazwa pliku wyjsciowego\n"); exit(1); } int main(int argc, char *argv[]) { /* zapisanie nazwy programu, bo potem bedzie zly dostep */ program_name = argv[0]; /* przetwarzanie opcji */ while ((argc > 1) && (argv[1][0] == '-')) { if (argv[1][1] == 'v') { /* -v tryb pelnej informacji */ verbose = TRUE; } else if (argv[1][1] == 'o') { outfile = &argv[1][2]; } else if (argv[1][1] == 'h') { usage(); } else { fprintf(stderr, "Nieprawidlowa opcja %s\n", argv[1]); usage(); } ++argv; /* przesuwa liste argumentow o jeden w gore */ --argc; /* zmniejsza licznik o jeden */ } /* * W tym miejscu opcje sa przetworzone. * Rozpoczynamy przetwarzanie podanych plikow lub infile. */ if (argc == 1) { przetwarzaj(infile); } else { while (argc > 1) { przetwarzaj(argv[1]); ++argv; --argc; } } return 0; }
Skompilować i uruchomić program sprawdzając różne opcje i argumenty.
Często programy w systemie Linux mogą pobierać ustawienia z wielu źródeł, tj. dana opcja może być ustawiana w wielu lokalizacjach. Zwykle ważność opcji maleje w następującej kolejności: