10. díl - Knihovny v jazyce C a C++

C++ Pokročilé konstrukce Knihovny v jazyce C a C++

Knihovny jsou nedílnou součástí programování. Obsahuji sadu funkcí a struktur, které spolu úzce souvisí. V článku o kompilaci jsme si řekli, že linker spojí soubory a přepočítá offsety jednotlivých volání. Pravou úlohou linkeru je ale připojení naší aplikace ke knihovnám. Narazí-li linker na použití knihovny, má v podstatě jen dvě možnosti. Buď může zkopírovat kód z knihovny do naší aplikace a nebo zařídit, aby potřebné funkce byly dostupné během běhu aplikace.

Rozdíl mezi statickou a dynamickou knihovnou

Statická knihovna je výsledek prvního přístupu. Kompiler zkopíruje zdrojové kódy z knihovny a umístí je mezi kódy naší aplikace. Statické knihovny mají zpravidla zkratku .a (pro Linux) nebo .lib (pro Windows). Dynamické knihovny se nepřidávají k našemu zdrojovému souboru, ale připojují se k aplikaci až za běhu. To vede na několik problémů, o kterých si řekneme později. Chceme-li použít dynamickou knihovnu, musíme ji distribuovat společně se samotnou aplikací. Dynamické knihovny mají koncovku .so (pro Linux) nebo .dll (pro Windows).

V dnešním díle si vytvoříme statickou a dynamickou verzi knihovny pro základní matematické operace. Protože je zdrojových souborů hodně, zdrojové kódy bude pouze zde a ve zbytku článku s nimi budeme pracovat.

//main.c
#include "operace.h"
int main()
{
        int a = 5;
        int b = 4;
        int s = secti(a,b);
        int v = vynasob(a,b);
        int o = odecti(a,b);
        int d = vydel(a,b);
        printf("Soucet=%d Soucin=%d Podil=%d Rozdil=%d",s,v,d,o);
        return 0;
}

//operace.h
#ifndef __OPERACE_H_
#define __OPERACE_H_
#include "scitani.h"
#include "nasobeni.h"
#include "odcitani.h"
#include "deleni.h"
#endif

//scitani.h
#ifndef __SCITANI_H_
#define __SCITANI_H_
int secti(int,int);
#endif

//scitani.c
#include "scitani.h"
int secti(int a, int b)
{
        return a+b;
}

//nasobeni.h
#ifndef __NASOBENI_H_
#define __NASOBENI_H_
int vynasob(int,int);
#endif

//nasobeni.c
#include "nasobeni.h"
int vynasob(int a,int b)
{
        return a*b;
}

//deleni.h
#ifndef __DELENI_H_
#define __DELENI_H_
int vydel(int,int);
#endif

//deleni.c
#include "deleni.h"
int vydel(int a,int b)
{
        return a/b;
}

//odcitani.h
#ifndef __ODCITANI_H_
#define __ODCITANI_H_
int odecti(int,int);
#endif

//odcitani.c
#include "odcitani.h"
int odecti(int a,int b)
{
        return a-b;
}

Tvoříme statickou knihovnu

Nejprve si všechny soubory zkompilujeme do objektových souborů:

$ gcc -c scitani.c
$ gcc -c nasobeni.c
$ gcc -c deleni.c
$ gcc -c odcitani.c

Nyní tyto soubory složíme dohromady pomocí příkazu ar.

$ ar rcs liboperace.a scitani.o nasobeni.o deleni.o odcitani.o

Příkaz ar je zkratka pro "archiver" a slouží k vytváření statických knihoven. r na začátku říká, že chceme soubory vložit nebo přepsat (replace), c zase že chceme archiv vytvořit (název archivu je první parametr - všimněte si řetězce "lib" na začátku). Nakonec pomocí s řekneme, že chceme přidat do archivu index (aby mohl linker soubory najít). Nyní zkompilujeme main.c a knihovnu přiložíme. Přepínačem -L určíme, kde se mají knihovny hledat (tečka znamená aktuální adresář). Přepínačem -l poté určíme, jaké knihovny se mají přiložit. Název knihovny je název souboru za řetězcem "lib" (proto je "lib" na začátku názvu knihovny důležité).

$ gcc -o program.exe main.c -L. -loperace
$ ./program.exe
Soucet=9 Soucin=20 Podil=1 Rozdil=1

Tím máme statickou knihovnu hotovou. Program můžeme bez problémů spustit.

Tvoříme dynamickou knihovnu

Dynamická knihovna se nahrává až za běhu aplikace a stejnou dynamickou knihovnu může používat několik aplikací současně. To znamená, že knihovna musí být nahrána někde v paměti a program musí vědět, kam má směřovat volání jednotlivých funkcí. Ale tuhle informací při kompilaci nevíme, dozvíme se ji až při samotném spuštění aplikace - to vede na takzvaný kód nezávislý na pozici (PIC - Position Independent Code). Současně stejně jako aplikace, musí být dynamická knihovna ve formátu, který procesor umí zpracovat (protože po odeslání na uživatelský počítač již neprobíhá žádná dodatečná kompilace), tzn. knihovna již musí být v binárním kódu - a to nám obstará samozřejmě kompiler. Nejprve si vytvoříme objektové soubory tak, aby používaly PIC (pomocí -f flagu):

$ gcc -c -fPIC scitani.c
$ gcc -c -fPIC nasobeni.c
$ gcc -c -fPIC deleni.c
$ gcc -c -fPIC odcitani.c

Nyní knihovnu zkompilujeme pomocí kompileru. Tomu ale musíme dát vědět, že má vytvořit pouze dynamickou (sdílenou) knihovnu, to uděláme přidám flagu -shared:

$ gcc -shared -o liboperace.so scitani.o nasobeni.o deleni.o odcitani.o

Vytvořil se nám soubor liboperace.so - naše požadovaná knihovna. Předposledním krokem je napojit knihovnu na aplikaci. Kompilace samotného programu je naprosto stejná, jako by se jednalo o statickou knihovnu.

$ gcc -o program.exe main.c -L. -loperace

Pozn. autora: Na Cygwinu jsem se v této fázi setkal s problémy. Linker hlásil, že knihovnu nemůže najít. Bohužel jsem nezjistil, jak chybu opravit. Na Linuxu příkaz funguje bez problémů.

Ale když se pokusíme program spustit, dostanem hlášku podobnou následující:

./program.exe: error while loading shared libraries: liboperace.so: cannot open shared object file: No such file or directory

To je v pořádku. Poslední co musíme udělat je přesunout naši knihovnu tak, aby ji systém nalezl. To už je pro jednotlivé systémy individuální. Například na mém Linuxu Mint to je adresář /usr/lib. Nyní již můžeme program bez problémů spustit.

Výhody a nevýhody

Nyní si rozebereme výhody a nevýhody jednotlivých přístupů.

Rozšířitelnost

Vývoj aplikace je přirozený a stejně tak i vývoj knihovny. Změníme-li statickou knihovnu, musíme celou aplikaci znovu linkovat, aby se navázala na novou verzi knihovny. Co víc, používáme-li takovou knihovnu u více aplikací, musíme každou aplikaci znovu linkovat. Nejhorší případ nastane, jestliže jsme aplikaci už rozdistribuovali mezi zákazníky. V takovém případě musíme nahradit celý program novým. U dynamické knihovny je to jednodušší. Pouze nahradíme knihovnu v adresáři. Díky dynamickému načítání si aplikace knihovnu při dalším spuštění automaticky načte a bude používat nový kód.

Závislost

Program používající dynamickou knihovnu je závislý na této knihovně. Problém může nastat v situaci, kdy se změní rozhraní knihovny takovým způsobem, že nová verze již nebude zpětně kompatibilní. V takovém případě nám nepomůže ani dynamické připojení knihovny, protože funkce, které používáme, již v nové verzi nemusí vůbec být. Naproti tomu u statické knihovny (která se do našich zdrojových kódů překopíruje) máme jistotu, že se nezmění. Nemusíme také řešit kompatibilitu mezi různými verzemi, protože vždy bude použita ta verze knihovny, se kterou jsme aplikaci kompilovali.

Výkon

Použití dynamické knihovny přináší další režii při spuštění a mírnou režii i během běhu aplikace. Při spuštění aplikace musí operační systém všechny požadované knihovny načíst. Zároveň s tím operační systém vygeneruje tabulku, kde je která funkce v paměti umístěna. Veškeré volání funkcí poté probíhá přes tuto tabulku a mírně snižuje výkon aplikace. Velmi pravděpodobně se také stane, že operační systém umístí knihovnu v paměti daleko. Pro delší vzdálenost je nutné vykonávat velké skoky v programu. Tyto skoky jsou nepatrně dražší než skoky na blízká místa. Statická knihovna je umístěna ve stejném souboru jako náš program, operační systém tedy natáhne celý soubor do paměti a je pravděpodobné, že knihovna bude blízko našemu kódu. To dovoluje používat kratší skoky.

Velikost

Jak již bylo několikrát řečeno, statická knihovna se překopíruje k našemu zdrojovému kódu. To sebou samozřejmě nese větší velikost výsledného souboru. Nutno podotknout, že v dnešní době to už není nic hrozného. Jestli bude mít spustitelný soubor 5MB nebo 10MB není důležité, když jeden obrázek dokáže zabrat úplně stejné místo. Dynamickou knihovnu může používat více aplikací současně, ale stačí, když je v paměti pouze jednou. Díky tomu zabere méně místa v RAM, ale opět se jedná pouze o malé velikosti, které jsou ke zbývajícímu obsahu aplikace (zpravidla) zanedbatelné.

Nyní jsme ukončili náš krátký teoretický výlet do kompilace v jazyce C (a C++). Příště se podíváme na testování programů v jazyce C.


 

  Aktivity (1)

Článek pro vás napsal patrik.valkovic
Avatar
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu.

Jak se ti líbí článek?
Celkem (1 hlasů) :
55555


 



 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!