Statické a sdílené knihovny

Statické knihovny

Statické knihovny už vlastně použít umíte, ikdyž o tom možná nevíte. Statickou knihovnou je objektový soubor, který má v sobě zakompilovanou definici funkce (například).

Při překladu se každá definice použité funkce zkopíruje do výsledného spustitelného souboru. V příkladu na vytváření objektového souboru jsem vytvořil dvě „knihovny“ – number1.o a number2.o.

Každá z těchto knihoven měla v sobě definovanou jen jednu funkci, ale v praxi jich budete mít v každé knihovně spíše více (nějak logicky souvisejících).

Skutečná (statická) knihovna je archív objektových souborů. Archív je jeden soubor, který v sobě obashuje více objektových souborů. Vytváří se pomocí programu ar.

Archiv vytvořený pomocí ar je něco podobného, jako je třeba tar archiv, nebo zip. (Ar ale není komprimovaný.) Hlavní výhoda archivu ar (třeba oproti tar archivu) je ta, že si s ním rozumí gcc/g++/clang.

Je zvykem, že statická knihovna má příponu .a a jméno začíná na lib.

V příkadu použiji zdrojové kódy z předchozí kapitoly:

$ gcc -c number1.c
$ gcc -c number2.c
$ ar -r libnumber.a number1.o number2.o
ar: creating libnumber.a
$ ar -t libnumber.a
number1.o
number2.o

Vytvořil jsem knihovnu libnumber.a, do které se vložily objekty number1.o a number2.o

Volby programu ar jsou následující:

Volba Význam
-r(replace) nahradí soubor v knihovně
-t(type) vypíše obsah knihovny
-x(extract) vyextrahuje soubor .o z knihovny
-d(delete) vymaže soubor z knihovny
-Szakáže vytváření indexu archivu, což urychlí vytvoření archivu. Index pak lze dodatečně vytvořit (když už bude knihovna hotová).
-svytvoří nebo aktualizuje index archivu.

V knihovně může být více souborů stejného jména. Je potřeba pužít volbu -r replace, aby se starý soubor stejného jména (pokud existuje) smazal a nahradil novým.

Index archivu můžete vytvořit příkazem ar -s archiv.a, nebo pomocí programu ranlib. Index archivu urychluje vyhledávání funkcí v archivu (a tím i překlad programů s archivem) a také umožňuje, aby se funkce v archivu volaly navzájem bez ohledu na pořadí, v jakém jsou v archivu definovány. V předchozím příkladě jsem nepoužil volbu -s, proto index v archivu není.

$ ranlib libnumber.a

A teď už tam je :-). Odkládat vytváření archivu až na konec má samozřejmě smysl jen u nějakých mega velkých knihoven.
Pokud jednou volbu -s použijete, bude index už aktualizován automaticky (i bez volby -s).

Užití

Knihovnu (.a) můžete použít při překladu stejně, jako se používají objektové soubory. Výhodu máte už v tom, že díky indexu najde překladač co potřebuje rychleji.

$ clang -o main main.c libnumber.a

Knihovny také můžete umístit do nějakého adresáře a překladači pak říct, aby je připojil pomocí volby -l.
Knihovny se hledají ve standardních adresářích (jako např. /usr/lib/). Pomocí volby -L můžete překladači říct o dalších adresářích, které má prohledávat.

$ clang -o main main.c -lnumber -L.

Jméno knihovny se píše bez úvodního lib a bez koncovky .a.
Tečka za -L překladači říká, aby prohledával aktuální adresář.

Místo volby -L si můžete nastavit proměnnou prostředí LD_LIBRARY_PATH. Cesty uložené v této proměnné (oddělené dvojtečkou) bude překladač také prohledávat. Tento způsob se ale spíše nedoporučuje. Závislost na LD_LIBRARY_PATH většinou vede ke zmatkům.

Sdílené knihovny

Statické knihovny jsou fajn, ale mají jisté nevýhody. Pokud funkci z knihovny budete používat v mnoha programech, každý si ponese vlastní kopii. Výhoda je, že statická knihovna už pro běh programu není pořeba, nevýhoda je, že když najdete ve funkci chybu, musíte překompilovat každý program, který funkci používal. A samozřejmě, rozkopírovaná funkce zabírá místo na disku (což s dnešními velikostmi disků už vlastně není takový problém :-).

Odpovědí jsou sdílené (shared) knihovny. Ty už jste také používali. Třeba knihovnu standardních funkcí jazyka C, libc.so (která se linkuje k programu automaticky), nebo matematickou knihovnu libm.so (kterou linkujete pomocí volby -lm). Pro C++ je tu zase knihovna libc++.

Jak vidíte, sdílené knihovny mají příponu .so (ve Windows .dll) a začínají také na lib.

Funkce ze sdílených knihoven, jak už asi tušíte, se do výsledného programu neukládají. Do programu se jen uloží odkaz na sdílenou knihovnu. Nevýhodou je, že se při spuštění programu musí načítat i sdílené knihovny, což zabírá nepatrně času navíc. A program bez sdílené knihovny fungovat nebude.

Teď už jen potřebujete vědět, jak sdílenou knihovnu vytvořit. K programu se připojuje stejně jako statická knihovna (za pomoci voleb -l a -L). Mimochodem, lepší než používat -L je nainstalovat sdílenou knihovnu do některého ze standardníh adresářů (najděte si, kde máte soubory *.so, viz třeba /usr/lib/, /usr/lib64/) nebo jeho podadresářů.

Position independent code

Pro začátek je pořeba vytvořit objektové soubory s pozičně nezávislým kódem (position-independent code, PIC). PIC je kód, který funguje bez ohledu na to, kde je uložen v paměti. Protože sdílenou knihovnu bude využívat více programů, nemůže knihovna ukládat data na fixní adresu. Podrobnosti nejsou důležité (můžete si je nastudovat na wiki, nebo zde, nebo zde (So what is "PIC" code?)), stačí vědět, že musíte při překladu shared knihoven použít volbu -fpic.

$ gcc -c -o snumber1.o number1.c -fpic -Wall
$ gcc -c -o snumber2.o number2.c -fpic -Wall

A teď můžete vytvořit sdílenou knihovnu:

$ gcc -shared -o libsnumber.so snumber1.o snumber2.o

Užití

Zkompilovat program s použitím sdílené knihovny už umíte. Zase platí, že musí být sdílená knihovna ve standardním adresáři pro sdílené knohovny, nebo musíte použít -L.

$ clang -o smain main.c -L. -lsnumber

Kdybyste náhodou pojmenovali sdílenou a statickou knihovnu stejně (jen s jinou koncovkou), tak ty sdílené mají přednost. Já sem sdílené knihovně na začátek jména přidal s (libsnumber.so), aby se to nepletlo.

Teď můžete spustit výsledný program smain. Pokud není sdílená knihovna ve standardním adresáři, musíte nastasvit proměnnou prostředí LD_LIBRARY_PATH.

$ ./smain
./smain: error while loading shared libraries: libsnumber.so: cannot open shared object file: No such file or directory
$ LD_LIBRARY_PATH=. ./smain
n1 = 1
n2 = 2
$ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
$ ./smain
n1 = 1
n2 = 2

Při druhém pokusu jsem nastavil LD_LIBRARY_PATH jen pro aktuálně spuštěný příkaz, což je způsob, který preferuji před použitím příkazu export.
Druhý způsob ukazuje, jak proměnné prostředí LD_LIBRARY_PATH na začátek přiřadit aktuální adresář (tečka) a nastavení exportovat, takže bude platné pro celé sezení v terminálu.

To je mimochodem docela nebezpečné, protože vám teď může nějaký útočník vložit do adresáře svojí hacknutou knihovnu libc.so (třeba do /tmp). Když pak spustíte program z tohoto adresáře, program načte útočníkovu knihovnu, místo té z /usr/lib/ …

Systémové sdílené knihovny

Pokud se rozhodnete uložit sdílenou knihovnu do systémových adresářů, musíte ještě spustit program ldconfig.

O načtení sdílených knihoven se stará knihovna ld.so nebo ld-linux.so. Tyto knihovny přilinkují sdílené knihovny k programu „za běhu.“

Bývá zvykem, že součástí názvu sdílené knihovny jsou čísla verzí a na knihovnu vedou symbolické linky. Například v mém OpenSuSE mám knihovnu libc-2.22.so, na kterou vede link libc.so.6. Programy se mi pak linkují s knihovnou libc.so.6.

Program ldconfig se stará o vytváření potřebných symbolických linků, ale také o vytvoření cache pro knihovny ld.so a ld-linux.so, která obsahuje seznam všech systémových statických khihoven. Pak nemusí ld.so a ld-linux.so při každém spuštění jakéhokoliv programu znovu a znovu prohledávat adresáře a hledat nejaktuálnější dostupné verze knihoven.

Instalace sdílené knihovny se tak sestává ze dvou kroků, které musíte provést jako root. Dejte si přitom pozor, ať si nepřepíšete nějakou existující systémovou knihovnu!

$ sudo cp -i libsnumber.so  /usr/lib64/
$ sudo /sbin/ldconfig
$ unset LD_LIBRARY_PATH
$ ./smain
n1 = 1
n2 = 2

Další příklady a podrobnosti můžete najít na cprogramming.com

Program pkg-config

Některé knihovny jsou nainstalované v nestandardních adresářích, nebo potřebují k překladu hlavičkové soubory z nestandardních adresářů. Pak je obtíž zjistit, jaké použít -l, -L nebo -I. S tímto problémem může pomoci program pkg-config.

Program pkg-config spoléhá na konfigurační soubory (soubory s příponou .pc), které se instalují do adresáře /usr/share/pkgconfig/, nebo /usr/lib64/pkgconfig/, nebo nějakého podobného (podívejte se do manuálové stránky k pkg-config, kde se dočtete přesné informace). Tyto soubory vytváří tvůrci knihoven, takže se automaticky instalují při instalaci vývojových balíčků knihovny.

Použití je jednoduché. Pokud chcete zjistit volby potřebné pro překladač a linker, zadejte tyto příkazy:

$ pkg-config --cflags nazev_knihovny
$ pkg-config --libs nazev_knihovny

Například pro knihovnu ncursesw:

$ pkg-config --cflags ncursesw
-D_GNU_SOURCE -D_DEFAULT_SOURCE -I/usr/include/ncursesw
$ pkg-config --libs ncursesw
-lncursesw -ltinfow

Výsledek volání tohoto programu může být (a také bývá) na různých systémech různý. Proto je vhodné si zvyknout používat pkg-config pomocí zpětných uvozovek takto:

$ gcc -c `pkg-config --cflags ncursesw` neco-co-pouziva-ncuresw.c
$ gcc -o main neco-co-pouziva-ncuresw.o `pkg-config --libs ncursesw`

Nebo rovnou takto:

$ gcc -o main neco-co-pouziva-ncuresw.o `pkg-config --cflags --libs ncursesw`

V této kapitole program pkg-config není k ničemu potřeba. Tento odstavec měl za cíl vás seznámit s tím, jak tvůrci sdílených knihoven obvykle poskytují přístup k volbám potřebným pro překlad s jejich knihovnami (pomocí konfiguračních souborů pro program pkg-config).
V dalších kapitolách, kde budu představovat některé nestandardní knihovny, už pkg-config využijete.

Standardní knihovny, jako např. matematická knihovna libm, soubory .pc nedodávají, protože žádné nestandardní volby nepotřebují.

Výpis sdílených knihoven

Program ldd vám vypíše všechny sdílené knihovny, na kterých program závisí.

$ ldd smain
linux-vdso.so.1 (0x00007fff83bb0000)
libsnumber.so => /usr/lib64/libsnumber.so (0x00007ffeb59f8000)
libc.so.6 => /lib64/libc.so.6 (0x00007ffeb5648000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffeb5c00000)

Vidíte že jsem nelhal, k programu smain jsou přilinkované knihovny libc, ld-linux i libsnumber.so. Knihovna linux-vdso.so je tu kvůli nějakým systémovým voláním jádra, nic zajímavého :-).

Pokud knihovnu /usr/lib64/libsnumber.so smažete, dostanete takovýto výpis:

$ ldd smain
linux-vdso.so.1 (0x00007fff59298000)
libsnumber.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007ff7cc180000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff7cc530000)
Komentář Hlášení chyby
Created: 16.8.2014
Last updated: 15.11.2017
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..