Programowanie w C (index)


Programowanie w C (7) - preprocesor, argumenty i opcje programu

PREPROCESOR

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

ZADANIE 7.1

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:

ZADANIE 7.2

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)

ZADANIE 7.3

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).

ZADANIE 7.4

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.

ZADANIE 7.5

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.

ZADANIE 7.6

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:


Programowanie w C (index)