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: