Učíme se C (27. díl - závěr) - Programové moduly - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



C/C++

Učíme se C (27. díl - závěr) - Programové moduly

17. srpna 2001, 00.00 | Dělení programu do modulů, pravidla pro jejich vytváření a použití, hlavičkové soubory, výhody odděleného překladu.

Při programování, zvláště pak při práci na rozsáhlejších projektech, je vhodné rozdělit kód programu do více částí. Každá takováto část by pak měla sdružovat funkce určitého typu, například funkce pro práci s grafikou nebo se soubory. Výhodou je, že každá takováto část je víceméně nezávislá a dá se tak použít i při práci na jiných projektech.

Se znalostmi, které zatím máme, jsme tento styl práce mohli uplatňovat pomocí direktivy preprocesoru #include, která jednoduše do zdrojového souboru vložila jiný soubor. Tento způsob je ale, řekněme, jen na půli cesty a vlastně nepřináší příliš velké zjednodušení. Naštěstí ale jazyk C podporuje i oddělený překlad, tedy práci s tzv. moduly, které oproti prostému includování přinášejí mnohé výhody.

Oddělený překlad souborů

Již sám název napovídá, o co vlastně jde. Jednotlivé soubory, které jsme dříve prostě jen spojili do jednoho pomocí direktivy #include, jsou přeloženy každý samostatně. Po překladu jsou pak vzniklé soubory (moduly) spojeny do jednoho programu tzv. linkerem. Výhody jsou zřejmé. Při dalších překladech jsou již kompilovány pouze ty soubory, ve kterých skutečně došlo ke změně, což celý proces značně urychluje. Funkce i proměnné z různých modulů mohou i nemusí být sdíleny, a je tedy možné mít v různých modulech definovány různé funkce se stejným jménem. Další výhodou je pak možnost připojovat a používat i moduly vytvořené v jiném programovacím jazyku, či jiným programátorem.

Sdílení proměnných

Mezi moduly lze sdílet pouze globální datové objekty, neboť ty existují po celou dobu vykonávání programu. Nepřímo pak, voláním sdílené funkce, lze sdílet i statické lokální proměnné. Lokální automatické proměnné sdílet nelze, již kvůli omezené délce jejich trvání (po dobu vykonávání funkce v níž jsou definovány).

Sdílená proměnná musí být definována právě v jednom modulu. V ostatních modulech, ve kterých chceme k této proměnné přistupovat musí být tato proměnná již pouze deklarována (tedy již nedochází k dalšímu přidělení paměti, pouze je překladači oznámena její existence v jiném modulu).

Deklarace sdílené proměnné vypadá stejně jako její definice, jen s tím rozdílem, že před ní ještě uvedeme klíčové slovo extern. V deklaraci proměnné nikdy nesmíme použít její inicializaci. Překladač by pak deklaraci považoval za definici nehledě na klíčové slovo extern.

Následující příklad snad celou záležitost trochu objasní. Mějme dva soubory a.c a b.c, které budeme překládat odděleně, a ve kterých budeme chtít sdílet proměnné x, y a z.

//v souboru a.c:
int x;
int y;
extern int z;

//v souboru b.c:
extern int x;
int y;
extern int z;

Pouze proměnná x je sdílena správně. Proměnná y je definována v obou překládaných souborech a při spojení obou modulů linkerem by tedy došlo ke kolizi. Naopak proměnná z je v obou souborech pouze deklarována, ve skutečnosti tedy fyzicky neexistuje a nedá se tedy použít.

Nesdílené proměnné

Pokud chceme, aby globální proměnná byla viditelná pouze v rámci svého modulu, uvedeme před její definici klíčové slovo static (Pozor, static se používá i pro automatické lokální proměnné, ale v obou případech je význam jeho použití jiný. viz. Modifikátory paměťové třídy). Protože je taková proměnná viditelná pouze v modulu, kde je definována, v ostatních modulech mohou existovat jiné proměnné se stejným identifikátorem.

Úpravou předchozího příkladu získáme dvě proměnné s identifikátorem y, které jsou ovšem ,narozdíl od proměnné x, nezávislé (změna hodnoty y v jednom modulu neovlivní hodnotu y ve druhém) a navzájem si nepřekážejí.

//v souboru a.c:
int x;
static int y;

//v souboru b.c:
extern int x;
static int y;

Sdílení funkcí

Stejně jako u sdílení proměnných, platí, že i funkce musí být definována právě v jednom modulu a v ostatních modulech musí být uvedena už jen pouze její deklarace (hlavička). Použití klíčového slova extern u deklarací funkcí již není nutná, protože překladač dokáže bezpečně rozlišit deklaraci od definice i bez něho. Pro zvýšení čitelnosti programu lze ale jeho použití doporučit.

Nesdílené funkce vytvoříme, obdobně jako u proměnných, použitím klíčového slova static a i zde platí, že v různých modulech mohou existovat různé nesdílené funkce mající stejný identifikátor.

Vytváření modulů

Při vytváření vlastních modulů bychom se měli řídit několika konvencemi, které celou práci s moduly výrazně ulehčují? Nejprve je nutné ujasnit si samotné rozdělení kódu do jednotlivých modulů. Je jasné, že funkce by měly být do jednotlivých modulů sdružovány podle svého účelu, např. modul obsahující funkce pro práci s grafikou, pro práci s myší apod.

Další věcí, kterou bychom měli stanovit, je závislost jednotlivých modulů. Ideální je vytvářet závislosti ve tvaru stromu, kdy „vysokoúrovňové“ moduly využívají pouze funkcí modulů nižší úrovně. To sice často nejde splnit, přesto je ale dobré se k tomuto požadavku, v zájmu přehlednosti programu, alespoň přiblížit.

Jak už víme, chceme-li využít služeb určitého modulu, musíme nejprve do programu vložit deklarace jeho funkcí a proměnných. Je-li ale např. modul již zkompilovaný, nemusíme vůbec znát hlavičky jeho funkcí. Proto tvůrce modulu vytváří i tzv. hlavičkový soubor .h, kde jsou uvedeny všechny deklarace potřebné k použití modulu. Tento soubor se pak jednoduše includuje do programu.

Vytváříme-li modul sami, měli bychom k němu tedy vytvořit i příslušný hlavičkový soubor (stejného jména). Ten by se měl skládat z následujících částí:

  • definice všech poskytovaných symbolických konstant a parametrických maker
  • definice poskytovaných uživatelských typů
  • deklarace všech exportovaných proměnných a funkcí

Abychom zabránili případným vícenásobným deklaracím, které by mohly nastat při několikanásobném vložení tohoto hlavičkového souboru, celý jeho obsah ohraničíme na začátku zápisem

#ifndef KONSTANTA 
#define KONSTANTA 

a na konci

#endif

KONSTANTA zde představuje v podstatě libovolný řetězec znaků, který by ale měl být unikátní. V praxi se tato symbolická konstanta tvoří kombinací jména modulu, jména autora a případně dalších znaků, které řetězec definitivně odliší od všech ostatních symbolických konstant.

Samotný zdrojový soubor .c modulu by pak měl mít následující strukturu

  • inkluze příslušného hlavičkového souboru a všech ostatních potřebných .h souborů
  • definice globálních exportovaných proměnných, tedy těch, které mohou být viditelné i z jiných modulů
  • definice symbolických konstant, maker a uživatelských typů, které budou využívány pouze uvnitř tohoto modulu
  • definice globálních neexportovaných proměnných (jsou definovány jako static)
  • deklarace neexportovaných (static) funkcí
  • definice exportovaných funkcí
  • definice neexportovaných funkcí

Ukažme si nyní, jak by mohl takový modul vypadat:

//soubor modul.c:
#include 
#include    //vložení hlavičkového souboru modulu

int prom1;           //exportovaná proměnná
static int prom2;    // soukromá proměnná

static int fce2();   // deklarace neexportované funkce

int fce1()           //exportovaná funkce
{
...
}

static int fce2()    // neexportovaná funkce
{
...
}

//soubor modul.h:
#ifndef __MODUL_H_JOSEF_NOVAK__ 
#define __MODUL_H_JOSEF_NOVAK__ 

extern int prom1;    //deklarace exportované proměnné
int fce1();          //deklarace exportované fce

#endif

Používání modulů

Moduly vytvořené výše popsaným způsobem se požívají velice snadno. V programu, kde chceme používat proměnné nebo funkce jiného modulu, jednoduše includujeme hlavičkový soubor tohoto modulu. Tím do programu vložíme všechny informace, které jsou potřebné pro úspěšný překlad. Nakonec se přeložené soubory všech použitých modulů spojí linkerem do jednoho spustitelného souboru. Většina dnešních překladačů již ale přímo podporuje práci s odděleně překládanými moduly (formou tzv. „projektů“), a tak se uživatel o linkování souborů většinou vůbec nemusí starat a stačí mu pouze přidat soubory modulů do projektu.

Závěr

Tímto dílem končí i celý seriál článků o programování v jazyce C a já doufám, že byl (a snad i nadále bude) čtenářům Builder.cz užitečný. Zároveň bych ale chtěl poznamenat, že pokud jste se podle tohoto seriálu učili programovat v C, vaše cesta by neměla skončit spolu s tímto článkem. Myslíte-li to s programováním vážně, jistě se budete zajímat i o jazyk C++, který k ANSI C přidává mnoho nového (Přímo na Builder.cz se tomuto tématu věnuje seriál Objektově orientované programování v C++ kolegy Radima Dostála). Přeji vám tedy mnoho úspěšně odladěných programů a ještě více jejich spokojených uživatelů.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: