Dnes probereme hlavičkové soubory a metodiku práce při psaní projektů z více zdrojových souborů. Příkaz #include <stdio.h> už pro nás přestane být jen vložením tajemného souboru, který nějakým neznámým způsobem zpřístupní některé funkce ze standardní knihovny.
17.3.2005 15:00 | Jan Němec | read 68917×
DISCUSSION
Hlavičkové soubory
V minulých dílech jsme si ukázali některé konstrukce jazyka, které umožní
sestavit program z více zdrojových souborů. Jde zejména o hlavičky funkcí
a deklarace proměnných pomocí klíčového slova extern, dále makra
preprocesoru určená ke globálnímu použití a příkaz #include. Zatím jsme příliš
nepřemýšleli kam nejlépe příslušnou část kódu umístit. Pokud jsme v souboru
main.c užívali funkci z funkce.c, napsali jsme prostě na začátek main.c její
hlavičku. V případě globálních proměnných z funkce.c jsme stejným způsobem
přidali do main.c jejich extern deklaraci. Tímto způsobem se v praxi
neprogramuje. Bylo by jistě nepříjemné pro každou funkci opisovat hlavičku
tolikrát, v kolika souborech je použita.
Při nejčastějším způsobu práce odpovídá každému *.c souboru, který poskytuje
nějaké funkce a globální proměnné, jeden *.h soubor. Název se zpravidla liší
pouze příponou. V tomto *.h souboru jsou hlavičky všech funkcí určených
k použití z ostatních *.c souborů, dále extern deklarace globálních proměnných
a makra preprocesoru, která nějak souvisí s obsahem příslušného *.c souboru.
Hlavičkový *.h soubor obsahuje i definice uživatelských typů, ale to ještě
neumíme. Tento soubor se pak pomocí příkazu #include vkládá na začátek všech
*.c nebo i jiných *.h souborů, které jej potřebují.
Jednoduchý příklad se dvěma *.c soubory by mohl vypadat asi takhle:
funkce.h
funkce.c
void funkce(void) {
/* ... */
}
main.c
#include "funkce.h"
int main(void) {
funkce();
return 0;
}
Všimněte si, že souboru main.c v našem příkladu neodpovídá žádný main.h, neboť
v main.c neposkytujeme žádné funkce ani proměnné k použití z jiných souborů.
Problémy mohou vznikat při cyklické nebo jinak složité závislosti. Mějme
projekt ze tří *.c souborů: main.c, funkce.c a rutiny.c, main.c volá funkce
z obou zbývajících a funkce.c a rutiny.c jsou rovněž na sobě vzájemně závislé.
V tom případě vytvoříme hlavičkové soubory funkce.h a rutiny.h. Všechny tři
*.c soubory můžeme zahájit příkazy
#include "funkce.h"
#include "rutiny.h"
Zatím bude vše fungovat. Horší to bude, pokud vznikne závislost i mezi *.h
soubory. To se může snadno stát, pokud v funkce.h definujeme makro, které
používá jiné makro z rutiny.h. V tom případě umístíme i do funkce.h příkaz
Bohužel v *.c souborech bude rutiny.h inkludován celkem dvakrát, jednou přímo
a jednou prostřednictvím funkce.h. To se nebude líbit překladači. V takto
jednoduchém projektu z několika souborů lze vždy problém nějak obejít,
inkludovat jen některé hlavičkové soubory nebo naopak inkludovat všechny
v pevném pořadí a zrušit #include příkaz ve funkce.h atd.,
ale v praktických složitějších projektech podobné řešení často nelze provést.
Je proto třeba nějak zajistit, aby se obsah *.h souboru vkládal jen jednou a
to i v případě, že je příkaz #include zavolán (přímo nebo zprostředkovaně)
vícekrát. Řešení není příliš obtížné:
rutiny.h
#ifndef _RUTINY_H
#define _RUTINY_H
extern int globalni_promenna;
void rutiny(void);
#endif
Takto se skutečně v praxi programuje. Soubor se vkládá pouze pokud není
definováno makro s charakteristickým jménem, v opačném případě vloží
preprocesor příkazem #include nanejvýš pár prázdných řádek. Pokud se obsah
souboru vkládá, je prvním příkazem definice výše zmíněného makra, které
zabrání opakovanému vložení. Podobným způsobem je ošetřeno určitě alespoň 99%
hlavičkových souborů ze skutečných projektů, zcela jednotná není pouze metodika
pojmenování makra, lišit se mohou například množstvím a umístěním podtržítek
a setkáme se i s makry jako rutinyH a podobně.
Při tomto způsobu práce lze již s úspěchem psát velké projekty s vysokým
stupněm vzájemné závislosti jednotlivých částí. Zdrojové *.c soubory na sobě
záviset (přes volání funkce nebo použití proměnné) i cyklicky, závislost *.h
souborů (použití makra nebo uživatelského typu, příkaz #include) cyklus
obsahovat nesmí. Je běžné, že *.c soubor inkluduje větší množství *.h souborů
stejného projektu. Nikdy by nemělo být podstatné pořadí #include příkazů
a nemělo by ani vadit inkludování *.h souboru, které se nevyužije.
Popsaný způsob, kdy jednomu *.c souboru odpovídá právě jeden hlavičkový
soubor, je určitě nečastější, ale setkáme se i s rozdělením funkcí, proměnných,
maker a typů, které se všechny týkají jednoho *.c souboru, do více *.h,
například
funkce zvlášť, proměnné zvlášť atd. Poměrně běžný je i opačný přístup, kdy
se naopak vše uloží do jediného hlavičkového souboru, který tak pokrývá nějaký
logický celek složený z více *.c souborů. Pokud projekt obsahuje podmíněný
překlad závislý na definici makra (například přítomnost ladícího kódu,
platforma produktu, ...) a nechceme tuto definici předávat jako
parametr překladači, je vhodné všechny volby umístit do jediného hlavičkového
souboru. Ten se pak bude pomocí #include vkládat do všech ostatních souborů,
které obsahují podmíněný překlad.
Příklad pro dnešní díl
Ukážeme si kompletní zdrojový kód projektu ze dvou *.c souborů a jednoho *.h.
Ve funkce.c je funkce, která podle jednoduchého vzorečku sečte n přirozených
čísel, a globální proměnná. Ve funkce.h je hlavička funkce a deklarace
proměnné s extern. Z main.c se jen volá funkce a vypíše hodnota proměnné.
funkce.h
#ifndef _FUNKCE_H
#define _FUNKCE_H
extern int pocet_volani;
/* vrací 1 + 2 + 3 + ... + n */
int suman(int n);
#endif
funkce.c
int pocet_volani = 0;
int suman(int n) {
pocet_volani++;
return (n * (n + 1)) / 2;
}
main.c
#include <stdio.h>
#include "funkce.h"
int main(void) {
printf("1 + 2 + 3 + 4 + 5 = %i\n", suman(5));
printf("počet volání suman() = %i\n", pocet_volani);
return 0;
}
Příklad přeložíme nejjednodušeji
gcc main.c funkce.c -o priklad
Pokračování příště
V příštím dílu se naučíme překládat s pomocí příkazu make. To je
poslední věc, kterou zatím stále děláme jinak než ve skutečných projektech.