Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Projekty s více soubory

Zdrojový kód lze rozdělit do více souborů. Jeden soubor (.c) lze chápat jako samostatný modul, který může ostatním modulům poskytovat některé své funkce a globální proměnné, a ostatní si přitom ponechat soukromé.

Chceme-li, aby byla funkce nebo proměnná platná pouze v souboru, ve kterém jsou definovány, použijeme k tomu static. Toto klíčové slovo se píše před definici. Pokud se v jiném souboru nachází definice proměnné nebo funkce se stejným jménem, jedná se o jinou proměnnou (nebo funkci).

//
// proměnná private_variable a funkce private_function jsou viditelné
// pouze v souboru, kde se nachází následující definice
// 

static int private_variable;

static int private_function(int a, int b)
{
	return a + b;
}

Předtím než použíjeme globální proměnnou nebo funkci z jiného souboru, musíme je deklarovat s klíčovým slovem extern. U proměnných je povinné, u funkcí je nepovinné, ale je dobré jej tam psát.

//
// následující musí být před použitím dané proměnné nebo funkce
//

extern int variable_in_different_file;
extern int function_in_different_file(int, int);

Linkage

Každý identifikátor (jméno proměnné) má v souboru, kde je definován atribut linkage, který může nabývat tří hodnot.

  • no linkage. Tento atribut mají lokální proměnné.
  • external linkage. Identifikátor je viditelný pro kód vně modulu, ve kterém je definován. Identifikátor může být s external linkage definován nejvýše jednou v celém programu. Jinde musí být definován s internal linkage nebo bez linkage.
  • internal linkage. Identifikátor je viditelný pouze uvnitř .c souboru, kde je definován.

Vidíme tak, že obyčejné funkce a globální proměnné mají external linkage, funkce a proměnné definované s klíčovým slovem static mají internal linkage.

Je důležité si uvědomit, že external linkage není identifikátoru přidělen použitím slova extern.

// file1.c

int x;                             // external 
static int y;                      // internal 

static int sum(int a, int b)       // internal
{
	int s = a + b;                 // no linkage
	return s;
}

int product(int a, int b)          // external
{
	int p = a + b;                 // no linkage
	return p;
}
// file2.c

extern int x;                 // je to proměnná x z file1.c
extern int product(int,int);  // funkce product z file1.c

// file3.c

static int x;                 // toto je OK, má internal linkage
static int product(int, int)  // toto je OK, má internal linkage
{ 
	// code
}

// V souboru uz nesmime deklarovat sum nebo product s extern!!!

Klasické dělení na soubory

Definice proměnných a funkcí je v .c souborech (např. code.c). Funkce a proměnné, které nemají být viditelné vně souboru, ve kterém jsou definovány, definujeme se static. Funkční prototypy a proměnné, které mají být viditelné, deklarujeme v souboru .h (např. code.h) s extern. Soubory, které tyto funkce a proměnné chtějí používat, potom tento soubor vloží pomocí #include.

Hlavičkový soubor navíc obsahuje i definice typů a maker, které jsou pro daný .c soubor veřejné nebo potřebné, např. typy, které se vyskytují ve funkcích a proměnných v daném .h souboru.

Proces překladu

Pro každý .c soubor se provede následující.

  1. Spustí se preprocessor.

    # pouze preprocesor
    gcc -E code.c      # tiskne na stdout
    
  2. Překladač přeloží kód do jazyka symbolických adres.

    # preprocesor + preklad do symbolickych adres
    gcc -S code.c  # vytvoří soubor code.s
    
  3. Assembler poté kód přeloží do objektového souboru (strojový kód a další informace, např. poskytovaná jména funkcí a proměnných.)

    # vytvori objektovy soubour code.o
    gcc -c code.c
    
  4. Linker sloučí vytvořené objektové soubory, spojí je s knihovnami (např. standardní knihovnou) a vytvoří cílený binární soubor (spustitelný soubor, knihovnu).

    # code1.o, code2.o jsou objektové soubory
    gcc code1.o code2.o   # volá linker, např. ld
    

Argumenty funkce main

Funkce main může mít žádný nebo paramatery, její hlavička je potom

int main(int argc, char *arg[]);

Hodnoty parametrů je možné funkci předat při spuštění programu z příkazové řádky (terminálu, skriptu apod.). Do parametru argc se uloží jejich počet zvětšený o jedna, jejich hodnoty (jako řetězce) jsou v poli argv počínaje indexem 1; argv[0] je totiž řetězec odpovídající spuštěnému programu. (argc je tak velikost pole argv.)

Úkoly

  1. Napište program pro výpočet objemu a povrchu válce, pravidelného trojbokého, čtyřbokého a šestibokého hranolu. Parametry výpočtu by mělo být možné předávat programu při spuštění z příkazové řádky. Zdrojový kód programu by měl být rozdělen do 2 modulů. Modul hlavní funkce (soubor main.c) bude zajišťovat zpracování a případně načtení chybějící parametrů výpočtu, budou z něj volány funkce zajišťující vlastní výpočet a vypisovány vypočítané hodnoty na obrazovku. Druhý modul (soubory vypocet.h a vypocet.c) pak bude zajišťovat veškeré požadované výpočty. Při řešení úlohy dbejte všech zásad zmíněných na přednášce. zdroj

  2. Napište v jazyku C jednoduchou knihovnu funkcí pro vykreslování obrázků pomocí znaků (tzv. ASCII art). Knihovna by měla mít tyto vlastnosti:

    • Obrázky se budou vykreslovat pomocí plátna - dvojrozměrné matice, která bude obsahovat jednotlivé znaky.
    • Vykreslování se tedy neprovádí přímo na výstupu, ale pouze dochází ke změně daného plátna (struktura Canvas).
    • Je možné pracovat současně s několika plátny.
    • Je možné “vykreslovat” i za hranicí kreslící plochy, tyto body se ale nebudou při zobrazení plátna vykreslovat. Jinými slovy, při pokusu o kreslení mimo plátno nedojde k vyjímce při běhu programu.

    Knihovna by měla být samostatným modulem, bude tedy tvořena jedním zdrojovým a jedním hlavičkovým souborem. V knihovně vytvořte strukturovaný datový typ Canvas a dále definujte tyto funkce:

    //
    // Inicializuje plátno na prazdné plátno široké width a vysoké height znaků
    //
    void canvas_init(Canvas *c, int width, int height);
    
    //
    // Vrátí znak nacházející na řádku x ve sloupci y
    //
    int canvas_get_point(Canvas c, int x, int y);
    
    //
    // Nakreslí obdélník, jehož stěna je tvořena znakem ch
    //
    void canvas_draw_rect(Canvas *c, int x, int y, int width, int height, char ch);
    
    //
    //  Vyprázdní plátno
    //
    void canvas_clear(Canvas *c);
    
    //
    //  Vykreslí obsah plátna na standardní výstup/do souboru
    //
    void canvas_print(Canvas *c);
    void canvas_output(Canvas *c, FILE *f);
    
    //
    //  Uvolní vnitřní pamět plátna
    //
    void canvas_destroy(Canvas *c);