C/C++ (16) - Hlavičkové soubory

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 | přečteno 67936×

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

void funkce(void);

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

#include "rutiny.h"

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.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=704