IT rekvalifikácia. Seniorní programátori zarábajú až 6 000 €/mesiac a rekvalifikácia je prvým krokom. Zisti, ako na to!

17. diel - Knižnice v jazyku C a C ++

Knižnice sú neoddeliteľnou súčasťou programovania. Obsahuju sadu funkcií a štruktúr, ktoré spolu úzko súvisia. V článku o kompiláciu sme si povedali, že linker spojí súbory a prepočíta offsety jednotlivých hovorov. Pravou úlohou linker je ale pripojenie našej aplikácie ku knižniciam. Narazí Ak linker na použitie knižnice, má v podstate len dve možnosti. Buď môže skopírovať kód z knižnice do našej aplikácie a alebo zariadiť, aby potrebné funkcie boli dostupné počas behu aplikácie.

Rozdiel medzi statickú a dynamickú knižnicou

Statická knižnica je výsledok prvého prístupu. Kompiler skopíruje zdrojové kódy z knižnice a umiestni ich medzi kódmi našej aplikácie. Statické knižnice majú spravidla skratku .a (pre Linux) alebo .lib (pre Windows). Dynamické knižnice sa nepridávajú k nášmu zdrojovému súboru, ale pripájajú sa k aplikácii až za behu. To vedie na niekoľko problémov, o ktorých si povieme neskôr. Ak chceme použiť dynamickú knižnicu, musíme ju distribuovať spoločne so samotnou aplikáciou. Dynamické knižnice majú koncovku .so (pre Linux) alebo .dll (pre Windows).

V dnešnom diele si vytvoríme statickú a dynamickú verziu knižnice pre základné matematické operácie. Pretože je zdrojových súborov veľa, zdrojové kódy bude iba tu a vo zvyšku článku s nimi budeme pracovať.

//main.c
#include <stdio.h>
#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;
}

Tvoríme statickú knižnicu

Najprv si všetky súbory skompilujeme do objektových súborov:

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

Teraz tieto súbory zložíme dohromady pomocou príkazu ar.

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

Príkaz ar je skratka pre "Archiver" a slúži na vytváranie statických knižníc. r na začiatku parametrov (ars) hovorí, že chceme súbory vložiť alebo prepísať (replace), c zasa že chceme archív vytvoriť (názov archívu je prvý parameter - všimnite si reťazca "lib" na začiatku). Nakoniec pomocou s povieme, že chceme pridať do archívu index (aby mohol linker súbory nájsť). Teraz skompilujeme main.c a knižnicu priložíme. Prepínačom -L určíme, kde sa majú knižnice hľadať (bodka znamená aktuálny adresár). Prepínačom -l potom určíme, aké knižnice sa majú priložiť. Názov knižnice je názov súboru za reťazcom "lib" (preto je "lib" na začiatku názvu knižnice 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 statickú knižnicu hotovú. Program môžeme bez problémov spustiť.

Tvoríme dynamickú knižnicu

Dynamická knižnica sa nahráva až za behu aplikácie a rovnakú dynamickú knižnicu môže používať niekoľko aplikácií súčasne. To znamená, že knižnica musí byť nahraná niekde v pamäti a program musí vedieť, kam má smerovať volanie jednotlivých funkcií. Ale túto informácií pri kompilácii nevieme, dozvieme sa ju až pri samotnom spustení aplikácie - to vedie na takzvaný kód nezávislý na pozíciu (PIC - Position Independent Code). Súčasne rovnako ako aplikácia, musí byť dynamická knižnica vo formáte, ktorý procesor vie spracovať (pretože po odoslaní na užívateľský počítač už neprebieha žiadna dodatočná kompilácie), tzn. knižnica už musí byť v binárnom kóde - a to nám obstará samozrejme kompiler. Najprv si vytvoríme objektové súbory tak, aby používali PIC (pomocou -f Flag):

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

Teraz knižnicu skompilujeme pomocou kompileru. Tomu ale musíme dať vedieť, že má vytvoriť len dynamickú (zdieľanú) knižnicu, to urobíme pridám Flag -shared:

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

Vytvoril sa nám súbor liboperace.so - naše požadovaná knižnica. Predposlednom krokom je napojiť knižnicu na aplikáciu. Kompilácie samotného programu je úplne rovnaká, ako by sa jednalo o statickú knižnicu.

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

Pozn. autora: Na Cygwin som sa v tejto fáze stretol s problémami. Linker hlásil, že knižnicu nemôže nájsť. Bohužiaľ som nezistil, ako chybu opraviť. Na Linuxe príkaz funguje bez problémov.

Ale keď sa pokúsime program spustiť, dostaneme hlášku podobnú nasledujúce:

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

To je v poriadku. Posledné čo musíme urobiť je presunúť našu knižnicu tak, aby ju systém našiel. To už je pre jednotlivé systémy individuálne. Napríklad na mojom Linuxe Mint to je adresár / usr / lib. Teraz už môžeme program bez problémov spustiť.

Výhody a nevýhody

Teraz si rozoberieme výhody a nevýhody jednotlivých prístupov.

Rozšíriteľnosť

Vývoj aplikácie je prirodzený a rovnako tak aj vývoj knižnice. Zmeníme Ak statickú knižnicu, musíme celú aplikáciu znovu linkovať, aby sa nadviazala na novú verziu knižnice. Čo viac, ak používame takú knižnicu u viac aplikácií, musíme každú aplikáciu znova linkovať. Najhorší prípad nastane, ak sme aplikáciu už rozdistribuovali medzi zákazníkmi. V takom prípade musíme nahradiť celý program novým. U dynamické knižnice je to jednoduchšie. Iba nahradíme knižnicu v adresári. Vďaka dynamickému načítanie si aplikácie knižnicu pri ďalšom spustení automaticky načíta a bude používať nový kód.

Závislosť

Program používajúci dynamickú knižnicu je závislý na tejto knižnici. Problém môže nastať v situácii, keď sa zmení rozhranie knižnice takým spôsobom, že nová verzia už nebude spätne kompatibilné. V takom prípade nám nepomôže ani dynamické pripojenie knižnice, pretože funkcie, ktoré používame, už v novej verzii nemusí vôbec byť. Naproti tomu u statické knižnice (ktorá sa do našich zdrojových kódov prekopíruje) máme istotu, že sa nezmení. Nemusíme tiež riešiť kompatibilitu medzi rôznymi verziami, pretože vždy bude použitá tá verzia knižnice, s ktorou sme aplikáciu kompilovali.

Výkon

Použitie dynamické knižnice prináša ďalšie réžii pri spustení a miernou réžii aj počas behu aplikácie. Pri spustení aplikácie musia operačný systém všetky požadované knižnice načítať. Zároveň s tým operačný systém vygeneruje tabuľku, kde je ktorá funkcia v pamäti umiestnená. Všetky volania funkcií potom prebieha cez túto tabuľku a mierne znižuje výkon aplikácie. Veľmi pravdepodobne sa tiež stane, že operačný systém umiestni knižnicu v pamäti ďaleko. Pre dlhšiu vzdialenosť je nutné vykonávať veľké skoky v programe. Tieto skoky sú nepatrne drahšie ako skoky na blízke miesta. Statická knižnica je umiestnená v rovnakom súbore ako náš program, operačný systém teda natiahne celý súbor do pamäte a je pravdepodobné, že knižnica bude blízko nášmu kódu. To dovoľuje používať kratšie skoky.

Veľkosť

Ako už bolo viackrát povedané, statická knižnica sa prekopíruje k nášmu zdrojovému kódu. To so sebou samozrejme nesie väčšiu veľkosť výsledného súboru. Treba podotknúť, že v dnešnej dobe to už nie je nič hrozné. Ak bude mať spustiteľný súbor 5MB alebo 10MB nie je dôležité, keď jeden obrázok dokáže zabrať úplne rovnaké miesto. Dynamickú knižnicu môže používať viac aplikácií súčasne, ale stačí, keď je v pamäti len raz. Vďaka tomu zaberie menej miesta v RAM, ale opäť sa jedná iba o malej veľkosti, ktoré sú na zostávajúci obsahu aplikácie (spravidla) zanedbateľné.

Tým sme uzavreli teoretické vedomosti z kompilácie aj celú sériu pokročilých konštrukcií C ++. Vhodným pokračovím by by bolo začať s objektovo orientovaným programovania, ktoré neleznete tu.

V nasledujúcom cvičení, Riešené úlohy k 1.-4. lekciu pokročilých konštrukcií C ++, si precvičíme nadobudnuté skúsenosti z predchádzajúcich lekcií.


 

Predchádzajúci článok
Kompilácie v jazyku C a C ++ pokračovanie
Všetky články v sekcii
Pokročilé konštrukcia C ++
Preskočiť článok
(neodporúčame)
Riešené úlohy k 1.-4. lekciu pokročilých konštrukcií C ++
Článok pre vás napísal Patrik Valkovič
Avatar
Užívateľské hodnotenie:
1 hlasov
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity