Knihovny

Nyní už známe vše potřebné na to, abychom si rozdělili náš vlastní program do libovolného množství zdrojových souborů. Často také ale budeme chtít používat kód, který už před námi napsal někdo jiný. Pokud bychom si totiž museli vše psát od nuly, tak bychom se daleko nedostali1, respektive trvalo by nám to dlouho.

1I když napsat si nějaký systém "od nuly" je dobrý způsob, jak se zlepšit v programování.

Aby programátoři mohli sdílet svůj kód s ostatními programátory, tak využívají tzv. knihovny (libraries). Knihovna je kód, který řeší nějakou ucelenou funkcionalitu (např. vykreslování grafiky, sazbu fontů nebo kompresi dat) a obsahuje návod (dokumentaci), jak tento kód používat. Klíčové vlastnosti knihoven jsou znovupoužitelnost (můžeme je použít v různých programech) a abstrakce (nemusíme rozumět, jak knihovna funguje, pouze ji využijeme k vyřešení konkrétního problému).

Knihovna není program – neobsahuje žádnou funkci main a nelze ji ani přímo spustit. V kontextu jazyka C je knihovna typicky sada funkcí, struktur a globálních proměnných.

Například pokud bychom programovali hru, můžeme využít knihovny na vykreslení grafiky, na přehrávání zvuku, na snímání vstupu z klávesnice nebo myši atd. Náš kód se pak může zabývat zejména logikou hry a nemusí tolik řešit problémy, které již vyřešila spousta programátorů před námi.

Na internetu můžete naleznout tisice různých knihoven, které řeší rozlišné problémy.

Sdílení knihoven

Teoreticky bychom mohli knihovny používat prostě tak, že si nějakou najdeme na internetu, stáhneme její hlavičkové a zdrojové soubory k našemu programu a začneme je využívat. I když i tak to lze někdy udělat, není to obvyklé, protože tento přístup má spoustu nevýhod:

  • Jelikož obvykle nebudeme autory knihovny, kterou chceme použít, tak nemusíme ani být schopní danou knihovnu přeložit. Potřebuje daná knihovna konkrétní překladač nebo jeho specifické nastavení? Má závislosti na dalších knihovnách? Přeložit "cizí" knihovnu ze zdrojových souborů nemusí být zdaleka přímočaré.
  • Pokud dojde k vydání nové verze knihovny, která může přinášet opravy chyb a novou funkcionalitu, museli bychom (kromě potenciální úpravy našeho kódu) také překopírovat nebo správně upravit nové a změněné soubory knihovny, což by bylo náročné a náchylné na chyby.
  • Zdrojový kód knihoven není vždy zveřejněn, například aby si jejich autoři uchránili duševní vlastnictví. Často se tak setkáme se situací, že máme k dispozici pouze objektový kód (např. .so nebo .dll) a nemůžeme tak získat zdrojové soubory knihovny.

Z tohoto důvodu jsou knihovny obvykle sdíleny ve formě objektových souborů (ty obsahují implementaci funkcí) a odpovídajících hlavičkových souborů (ty obsahují deklarace, aby šlo knihovnu jednoduše používat).

Statické vs dynamické knihovny

Předávat překladači desítky či stovky objektových souborů by bylo docela nepraktické, proto se tyto soubory při distribuci knihovny balí do jednoho či více archivů, které mají standardizovaný formát a překladače s nimi umí přímo pracovat. Knihovna může být distribuována v jednom ze dvou typů archivů, které určují to, jak bude daná knihovna "přilinkována" (připojena) k našemu programu:

  • Dynamická knihovna (dynamic library) - objektové soubory takovéto knihovny nebudou součástí našeho programu (tj. nebudou obsaženy ve spustitelném souboru, který bude vytvořen překladačem). K jejich načtení dojde až "dynamicky" při spuštění programu2.

    2O toto načítání se stará tzv. dynamický linker.

    Výhody tohoto přístupu jsou, že bude mít náš spustitelný soubor menší velikost, a to jak na disku, tak v operační paměti. Operační systémy totiž dokážou stejnou dynamickou knihovnu částečně sdílet mezi více běžícími programy najednou. Dynamickou knihovnu také půjde aktualizovat bez nutnosti překládat znovu náš program a můžeme také při spuštění programu knihovnu nahradit jinou implementací.

    Nevýhodou je, že při spuštění našeho programu musíme zajistit, že knihovna bude na daném systému k dispozici (pokud by nebyla nalezena, tak program nepůjde spustit). To může způsobovat problémy zejména při distribuci našeho programu na jiné počítače. Kvůli tomu, že se knihovna načítá dynamicky, také může v určitých případech být její použití méně efektivní než v případě statické knihovny.

    Archivy s objektovými soubory dynamických knihoven mají příponu .so.

  • Statická knihovna (static library) - objektové soubory takovéto knihovny budou přímo přibaleny k našemu programu (jako bychom je přímo jeden po druhém předali překladači).

    Výhody tohoto přístupu jsou, že náš program bude "samostatný" – knihovnu bude obsahovat uvnitř svého spustitelného souboru, takže nebude nutné ji mít dostupnou na cílovém systému (narozdíl od dynamické knihovny).

    Nevýhodou je, že výsledný spustitelný soubor bude větší a knihovnu nepůjde aktualizovat bez opětovného překladu celého programu.

    Archivy s objektovými soubory statických knihoven mají příponu .a.

Názvy přípon statických a dynamických knihoven závisí na operačním systému. Například na Windows se můžete setkat s příponami .lib pro statické knihovny a .dll pro dynamické knihovny.

Použití knihoven s gcc

Nyní si ukážeme, jak říct překladači gcc, aby připojil nějakou knihovnu k našemu programu. Pro to musíme mít k dispozici archiv s objektovými soubory knihovny (s příponou .a nebo .so, v závislosti na typu knihovny) a obvykle také i adresář s hlavičkovými soubory knihovny.

Nejprve si ukážeme, jak překladači předat cestu k hlavičkovým souborům knihovny. Ty obvykle nebudou součástí našich zdrojových kódů, ale budou nainstalovány v nějakém systémovém adresáři (jako tomu je např. u stdio.h). Budeme je tedy chtít vkládat pomocí syntaxe #include <>. Překladači můžeme předat dodatečné adresáře, ve kterých má hledat (hlavičkové) soubory pro vkládání, pomocí přepínače -I. Pokud bychom tak měli hlavičkové soubory knihovny např. v adresáři /usr/foo/include, tak překladači při překladu předáme přepínač -I/usr/foo/include.

Dále je třeba překladači říct, které archivy s objektovými soubory knihovny má k našemu programu přilinkovat. K tomu slouží dva přepínače. -L udává adresář, ve kterém se budou vyhledávat knihovny a -l poté specifikuje konkrétní knihovnu, která má být přilinkována k našemu programu. Pokud bychom tak měli například archiv knihovny v souboru /usr/foo/lib/libknihovna.so, tak překladači předáme parametry-L/usr/foo/lib a -lknihovna. Při použití přepínače -l je třeba si dávat pozor na dvě věci:

  • Všimněte si, že se použila zkrácená konvence pro pojmenování knihovny. Obecně se knihovny pojmenovávají lib<název>.so (nebo lib<název>.a) a překladači se poté předává pouze jejich název, tj. -l<název>.
  • Přepínač -l se aplikuje na zdrojové/objektové soubory, které byly v příkazové řádce zadány před ním. Používejte jej tedy až po předání vašich zdrojových souborů:
    # správně
    $ gcc main.c -lknihovna
    
    # špatně
    $ gcc -lknihovna main.c
    

Celý příkaz pro připojení knihovny k vašemu programu by tak mohl vypadat např. takto:

$ gcc -o program main.c -L/usr/foo/lib/ -lfoo -I/usr/foo/include

Předání cesty k dynamické knihovně

Pokud přeložíte program s dynamickou knihovnou, může se stát, že při jeho spuštění nebude schopen danou knihovnu najít. V takovém případě při spuštění programu můžete pomocí proměnné prostředí3 (environment variable) LD_LIBRARY_PATH předat cestu k adresáři, ve které se daná knihovna nachází:

3Proměnné prostředí jsou způsobem, jak předávat parametry programům (podobně jako například parametry příkazového řádku). V programu si můžete přečíst hodnotu konkrétní proměnné prostředí pomocí funkce getenv.

$ LD_LIBRARY_PATH=/usr/foo/lib ./program

Zobrazení vyžadovaných dynamických knihoven

Pokud si přeložíte nějaký program a použijete na něj program ldd, dozvíte se, které dynamické knihovny vyžaduje ke svému běhu. Měli byste mezi nimi naleznout mj. i standardní knihovnu C (libc) a dozvědět se tak její umístění na disku:

$ ldd ./program
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d3a328000)

Standardní knihovna jazyka C je používána téměř každým programem a i z tohoto důvodu je obvykle linkována dynamicky, aby její paměť šla sdílet mezi programy.

Vytvoření knihovny

Pokud byste si chtěli vytvořit vlastní knihovnu, můžete toho jednoduše dosáhnout pomocí gcc. Dejme tomu, že máte soubory a.c a b.c, které chcete zabalit do knihovny. Nejprve každý zdrojový soubor přeložíme do objektového souboru4:

4Parametr -fPIC je nutný při překladu zdrojových souborů, které poté chceme umístit do knihovny. Více se můžete dozvědět např. zde.

$ gcc -c -fPIC a.c
$ gcc -c -fPIC b.c

Další postup závisí na tom, jaký typ knihovny chceme vytvořit:

  • Vytvoření statické knihovny - použijeme program ar (archiver):
    $ ar rcs libknihovna.a a.o b.o
    
  • Vytvoření dynamické knihovny - použijeme program gcc s přepínačem -shared:
    $ gcc -shared a.o b.o -o libknihovna.so