kapitola 5

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 své funkce a globální proměnné, a některé může mít 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.

Příklad

// 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
}

//  pozor, ale 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ě souborů, 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 také obsahuje 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: hlavně vložení obsahu souborů připojených pomocí #include, vynechání části souborů (#ifdef), expanze maker (#define), odstranění komentářů.

    gcc -E code.c      # tiskne na stdout
  2. Překladač přeloží kód do jazyka symbolických 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.)

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