Hlavičkové soubory
Nyní už víme, že pro použití kódu z jiných souborů bychom nejprve měli dané funkce a proměnné deklarovat. Pokud bychom však museli v každém souboru, ve kterém chceme použít kód z jiného souboru, museli vytvářet deklarace pro každou funkci či proměnnou, kterou chceme použít, bylo by to docela zdlouhavé. Pokud by navíc došlo ke změně datového typu či názvu takovéto sdílené funkce či proměnné, museli bychom deklarace upravit ve všech souborech, kde funkci či proměnnou používáme.
Pro vyřešení tohoto problému se v C často využívá koncept tzv. hlavičkových souborů
(header files). Pro každý zdrojový soubor, jehož kód chceme sdílet, vytvoříme hlavičkový soubor,
který bude obsahovat deklarace všech sdílených veřejných funkcí a globálních proměnných z daného
zdrojového souboru. Ve zdrojovém souboru pak budou jejich definice. Dle jmenné konvence se hlavičkový
soubor pojmenovává jako <název zdrojového souboru>.h
:
// soubor.h (deklarace)
int moje_funkce();
extern int moje_promenna;
// soubor.c (definice)
int moje_funkce() {}
int moje_promenna;
Hlavičkový soubor tak udává tzv. rozhraní (interface) odpovídajícího zdrojového souboru – obsahuje seznam funkcí a proměnných, které jsou sdílené a zbytek programu je může používat.
Ostatní soubory, které chtějí funkce z nějakého zdrojového souboru použít, pak vloží jeho hlavičkový soubor pomocí preprocesoru, aby mohly používat sdílené funkce a globální proměnné s korektní kontrolou datových typů:
// main.c
#include "soubor.h"
int main() {
moje_funkce();
int x = moje_promenna;
return 0;
}
Pokud dojde ke změně signatury funkce či typu/názvu proměnné, tak stačí změnu udělat v hlavičkovém
(a odpovídajícím zdrojovém) souboru. Všechny ostatní soubory, které danou funkci nebo proměnnou
používají, pak budou okamžitě využívat upravenou deklaraci díky použití #include
.
S hlavičkovými soubory jsme již setkali při použití standardní knihovny. V
souborech jako je stdio.h
se nacházejí deklarace funkcí jako je například printf
, jejichž definice
je poté obsažena v objektových souborech standardní knihovny.
Obsah hlavičkového souboru
Jelikož hlavičkové soubory jsou určeny k tomu, aby byly využívány (vkládány) v různých zdrojových souborech, tak se jejich obsah přirozeně může vyskytnout ve více jednotkách překladu. Aby tak nebylo porušeno pravidlo jedné definice, je důležité do hlavičkových souborů dávat pouze deklarace, a ne definice funkcí a proměnných!
Pokud byste do hlavičkového souboru dali například definici funkce, a tento soubor by se vyskytnul
ve více jednotkách překladu, tak by linkování selhalo kvůli vícenásobné definici. Pokud byste
přecejenom opravdu chtěli definici nějaké funkce "propašovat" do hlavičkového souboru, můžete před
ní použít klíčové slovo inline
:
// soubor.h
inline void moje_funkce() { ... }
Tímto klíčovým slovem slibujete linkeru, že všechny definice funkce s tímto názvem jsou stejné.
Pokud tak linker narazí na definici této funkce vícekrát (což nastane, když tento hlavičkový soubor
bude vložen ve více jednotkách překladu), tak nebude hlásit chybu, ale prostě si jednu z těchto
definicí vybere. Pokud by definice stejné nebyly, může to vést k nedefinovanému chování
💣. Pokuste se tak inline
raději nevyužívat.
U (globálních) proměnných nemá smysl
inline
používat.
Kromě deklarací funkcí a proměnných se do hlavičkových souborů také běžně vkládají struktury, které jsou součástí typů sdílených proměnných či parametrů a návratových hodnot sdílených funkcí.
Aby mohly zdrojové soubory používat sdílené struktury i sdílené funkce v libovolném pořadí, tak obvykle zdrojové soubory vkládají svůj vlastní hlavičkový soubor:
// soubor.h
typedef struct {
int vek;
} Osoba;
int zpracuj_osobu(Osoba osoba);
// soubor.c
#include "soubor.h"
int zpracuj_osobu(Osoba osoba) { ... }
Pro použití struktur nebo např.
typedefů
z ostatních souborů
je také běžné, že hlavičkové soubory vkládají jiné hlavičkové soubory.
Ochrana vkládání
U hlavičkových souborů je nutné řešit ještě jednu další věc. Jelikož se běžně používají v kombinaci
s #include
, může se stát, že i v rámci jedné jednotky překladu se jeden hlavičkový soubor vloží do
výsledného zdrojového souboru více než jednou. To může způsobovat různé typy problémů:
- Pokud se budou hlavičkové soubory vkládat navzájem, mohlo by dojít k cyklické závislosti. Například
zde by překlad selhal, protože by se hlavičkové soubory snažili vložit se navzájem donekonečna:
// a.h #include "b.h" // b.h #include "a.h"
- Hlavičkový soubor se zbytečně vícekrát načítá překladačem, což prodlužuje dobu překladu.
- Pokud by hlavičkový soubor obsahoval nějakou definici, tak i kdyby byl použit pouze v jedné jednotce překladu, došlo by k chybě při linkování, protože by definice byla zduplikovaná.
Abychom těmto situacím zamezili, tak u hlavičkových souborů budeme používat tzv. ochranu vkládání (include guard). Pomocí ochrany vkládání zajistíme, že jeden hlavičkový soubor se v rámci jedné jednotky překladu vloží maximálně jednou.
Zamezení vícenásobného vložení můžeme dosáhnout pomocí podmíněného překladu:
// soubor.h
#ifndef SOUBOR_H
#define SOUBOR_H
void moje_funkce();
#endif
Tohle je nicméně trochu zdlouhavé. Moderní překladače obsahují mnohem jednodušší způsob. Na začátek
hlavičkového souboru stačí vždy vložit řádek #pragma once
a dál nemusíte nic řešit:
// soubor.h
#pragma once
void moje_funkce();