Czym jest biblioteka

edytuj

Biblioteka[1] jest to zbiór funkcji, które zostały wydzielone po to, aby dało się z nich korzystać w wielu programach. Ułatwia to programowanie - nie musimy np. sami tworzyć funkcji printf. Każda biblioteka posiada swoje pliki nagłówkowe, które zawierają deklaracje funkcji bibliotecznych oraz często zawarte są w nich komentarze, jak używać danej funkcji. W tej części podręcznika nauczymy się tworzyć nasze własne biblioteki.

Biblioteka

  • składa się co najmniej z dwóch plików: jeden nagłówkowy (źródłowy, API) i jeden binarny (skompilowany)
  • zawiera funkcje (deklaracje w nagłówkowym i definicje w binarnym)
  • w postaci pakietu (ang. package) może dzielić się na pakiety dev i non-dev[2]
  • jest przeznaczona do wykonania odrębnego zadania programistycznego
  • ma ściśle określony interfejs [3]
  • "Moduł ma charakter czarnej skrzynki (ang. black-box approach). Na zewnątrz modułu widoczne są wyłącznie te obiekty programistyczne, które tworzą interfejs. Natomiast sposób ich implementacji, jak i ewentualne obiekty pomocnicze są ukryte wewnątrz modułu."

Zasady budowy bibliotek ( modułów) wg strony wazniak.mimuw.edu.pl: [4]

  • powiązania między modułami powinny być jak najmniejsze;
  • jak najmniej szczegółów budowy jednego modułu miało wpływ na budowę innego modułu,
  • każdy moduł powinien koncentrować się wokół jednej decyzji projektowej, tzw. "sekretu" modułu, przy czym nie należy łączyć nie związanych ze sobą sekretów w jednym module; zasada ta jest znana pod nazwą separation of concerns,
  • użytkownicy modułów powinni polegać jedynie na tym, co jest określone w interfejsie i specyfikacji modułu, natomiast nie powinni polegać na żadnym konkretnym sposobie implementacji modułu, tzw. black-box approach.

wg


wg sposobu wykorzystania

edytuj
  • statyczne (ang. static library or statically-linked library) [5]
    • windows: .lib lub .obj
    • Unix: .a lub .o
  • dynamiczne[6]
    • biblioteka łączona dynamicznie,
      • Unix: biblioteka współdzielona (ang. shared library[7][8], shared object) .so, ścieżki poszukiwań plików bibliotek zapisane są w pliku /etc/ld.so.conf oraz w zmiennej środowiskowej $LD_LIBRARY_PATH.
      • Windows: .dll
    • biblioteki ładowane dynamicznie



wg autora

edytuj
  • standardowe - biblioteka zawierająca podstawowe funkcje, dostarczana wraz z kompilatorem lub interpreterem danego języka programowania. Nie wymagaja osobnej instalacji
  • niestandardowe, zewnętrzne, wymagajace osobnej instalacji

wg rozszerzenie

edytuj
  • a - biblioteki linkowane statycznie
  • bin - pliki binarne
  • fw - pliki firmware : biblioteki lub sterowniki sprzętu
  • ko - pliki dla modułów jądra
  • o - pliki obiektowe, np. ładowalne moduły jądra
  • so - dynamicznie linkowane biblioteki dzielone[9]

Ścieżka wyszukiwania

edytuj

Gcc w systemie Linuks wyszukuje nagłówki #include <file> w kilku standardowych katalogach: [10][11]

 /usr/local/include
 libdir/gcc/target/version/include
 /usr/target/include
 /usr/include

Możemy to sprawdzić za pomocą przełączników przy kompilacji:

gcc c.c -v -c 

przykładowy wynik:

ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc/x86_64-linux-gnu/4.8/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.

Zmienna środowiskowa LD_LIBRARY_PATH zawiera ścieżki do katalogów bibliotek. Te katalogi kompilator będzie przeszukiwał w trakcie kompilacji:[12]

LD_LIBRARY_PATH

W celu dopisania ścieżki do własnej biblioteki ustaw wartość zmiennej środowiskowej, na przykład w konsoli wpisz:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib32:/usr/lib32

gdzie /lib32 i /usr/lib32 są dopisywanymi ścieżkami do bibliotek (plików *.so). Jak widzimy w przykładzie, ścieżki oddzielamy dwukropkiem.


Sprawdzamy jakie ścieżki są przeszukiwane:[13]

 echo | gcc -Wp,-v -x c++ - -fsyntax-only

przykładowy wynik:

ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/4.8"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/4.8/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/include/c++/4.8
 /usr/include/x86_64-linux-gnu/c++/4.8
 /usr/include/c++/4.8/backward
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/4.8/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

Zależności

edytuj
  • ldd (
  • wyszukiwanie w kodzie [14]

Jak zbudowana jest biblioteka

edytuj

Kod źródłowy każdej biblioteki składa się z co najmniej dwóch części (oczywiście w praktyce nie istnieje górny limit):

  • pliku nagłówkowego z deklaracjami funkcji (plik z rozszerzeniem .h)
  • pliku źródłowego, zawierającego ciała funkcji (plik z rozszerzeniem .c)

Biblioteka po skompilowaniu składa się z dwóch plików:

  • nagłówkowy (źródłowy)
  • binarny (skompilowany)

Budowa pliku nagłówkowego

edytuj

Plik nagłówkowy zawiera interfejs programowania aplikacji ( ang. application programming interface = API)

Oto najprostszy możliwy plik nagłówkowy:

 #ifndef PLIK_H
 #define PLIK_H
 /* tutaj są wpisane deklaracje funkcji */
 #endif

Zapewne zapytasz się na co komu instrukcje #ifndef, #define oraz #endif. Otóż często się zdarza, że w programie korzystamy z plików nagłówkowych, które dołączają się wzajemnie. Oznaczałoby to, że w kodzie programu kilka razy pojawiła by się zawartość tego samego pliku nagłówkowego. Instrukcja #ifndef i #define temu zapobiega. Dzięki temu kompilator nie musi kilkakrotnie kompilować tego samego kodu.


W plikach nagłówkowych często umieszcza się też definicje typów, z których korzysta biblioteka albo np. makr.

Budowa najprostszej biblioteki

edytuj

Załóżmy, że nasza biblioteka będzie zawierała jedną funkcję, która wypisuje na ekran tekst "pl.Wikibooks". Utwórzmy zatem nasz plik nagłówkowy:

 #ifndef WIKI_H
 #define WIKI_H
 #include <stdio.h>
 void wiki (void) {printf("pl.Wikibooks\n");}
 #endif

Należy pamiętać, o podaniu void w liście argumentów funkcji nie przyjmujących argumentów. O ile przy definicji funkcji nie trzeba tego robić (jak to często czyniliśmy w przypadku funkcji main) o tyle w prototypie brak słówka void oznacza, że w prototypie nie ma informacji na temat tego jakie argumenty funkcja przyjmuje.

Plik nagłówkowy zapisujemy jako "wiki.h".

Ważne jest dołączenie na początku pliku nagłówkowego. Dlaczego? Plik nagłówkowy zawiera deklaracje naszych funkcji - jeśli popełniliśmy błąd i deklaracja nie zgadza się z definicją, kompilator od razu nas o tym powiadomi. Oprócz tego plik nagłówkowy może zawierać definicje istotnych typów lub makr. Napiszmy nasz program:

 #include "wiki.h"
 
 int main ()
 {
   wiki();
   return 0;
 }

Zapiszmy program jako "main.c" Teraz musimy odpowiednio skompilować nasz program:

gcc main.c -o main

Uruchamiamy nasz program:

./main
pl.Wikibooks

Jak widać nasza pierwsza biblioteka działa.

Zmiana dostępu do funkcji i zmiennych (static i extern)

edytuj

Język C, w przeciwieństwie do swego młodszego krewnego - C++ nie posiada praktycznie żadnych mechanizmów ochrony kodu biblioteki przed modyfikacjami. C++ ma w swoim asortymencie m.in. sterowanie uprawnieniami różnych elementów klasy. Jednak programista, piszący program w C nie jest tak do końca bezradny. Autorzy C dali mu do ręki dwa narzędzia: extern oraz static. Pierwsze z tych słów kluczowych informuje kompilator, że dana funkcja lub zmienna istnieje, ale w innym miejscu, i zostanie dołączona do kodu programu w czasie łączenia go z biblioteką.

extern przydaje się, gdy zmienna lub funkcja jest zadeklarowana w bibliotece, ale nie jest udostępniona na zewnątrz (nie pojawia się w pliku nagłówkowym). Przykładowo:

 /* biblioteka.h */
#ifndef BIBLIOTEKA_H
#define BIBLIOTEKA_H
 extern char zmienna_dzielona[];
#endif
 /* biblioteka.c */
 #include "biblioteka.h"
 
 char zmienna_dzielona[] = "Zawartosc";

 /* main.c */
 #include <stdio.h>
 #include "biblioteka.h"
 
 int main() 
 {
   printf("%s\n", zmienna_dzielona);
   return 0;
 }

Gdybyśmy tu nie zastosowali extern, kompilator (nie linker) zaprotestowałby, że nie zna zmiennej zmienna_dzielona. Próba dopisania deklaracji char zmienna_dzielona[]; stworzyłaby nową zmienną i utracilibyśmy dostęp do interesującej nas zawartości.

Odwrotne działanie ma słowo kluczowe static użyte w tym kontekście (użyte wewnątrz bloku tworzy zmienną statyczną, więcej informacji w rozdziale Zmienne). Może ono odnosić się zarówno do zmiennych jak i do funkcji globalnych. Powoduje, że dana zmienna lub funkcja jest niedostępna na zewnątrz biblioteki[15]. Możemy dzięki temu ukryć np. funkcje, które używane są przez samą bibliotekę, by nie dało się ich wykorzystać przez extern.

Rozwiązywanie problemów

edytuj
  • ustal jaką bibliotekę potrzebujesz
  • czy ta biblioteka jest zainstalowana?
    • jeśli tak to gdzie
    • jeśli nie to zainstaluj
  • ścieżki
    • gdzie jest zainstalowana biblioteka (pliki binarne i nagłówkowe)
    • gdzie kompilator/program wyszukuje biblioteki
    • pokaż kompilatorowi/programowi gdzie szukać bibliotekę

Przykłady

edytuj


Wg zastosowania

Common components of klib

  • khash.h: generic hash table with open addressing.
  • kbtree.h: generic search tree based on B-tree.
  • kavl.h: generic intrusive AVL tree.
  • ksort.h: generic sort, including introsort, merge sort, heap sort, comb sort, Knuth shuffle and the k-small algorithm.
  • kseq.h: generic stream buffer and a FASTA/FASTQ format parser.
  • kvec.h: generic dynamic array.
  • klist.h: generic single-linked list and memory pool.
  • kstring.{h,c}: basic string library.
  • kmath.{h,c}: numerical routines including MT19937-64 pseudorandom generator, basic nonlinear programming and a few special math functions.
  • ketopt.h: portable command-line argument parser with getopt_long-like API.

Components for more specific use cases

  • ksa.c: constructing suffix arrays for strings with multiple sentinels, based on a revised SAIS algorithm.
  • knetfile.{h,c}: random access to remote files on HTTP or FTP.
  • kopen.c: smart stream opening.
  • khmm.{h,c}: basic HMM library.
  • ksw.(h,c}: Striped Smith-Waterman algorithm.
  • knhx.{h,c}: Newick tree format parser.


Przypisy