C/Biblioteki
Czym jest biblioteka
edytujBiblioteka[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.
Pliki
edytujBiblioteka
- 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]
cechy
edytuj- 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.
typy
edytujwg
- architektury
- sposobu wykorzystania
- autora
- rozszerzenia
wg sposobu wykorzystania
edytuj- statyczne (ang. static library or statically-linked library) [5]
- windows: .lib lub .obj
- Unix: .a lub .o
- dynamiczne[6]
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
edytujGcc 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
edytujKod ź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
edytujPlik 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
edytujZałóż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)
edytujJę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- biblioteka standardowa
- libm = biblioteka matematyczna
- POSIX Threads (w skrócie pthreads)= biblioteka udostępniająca jednolite API dla języka C do tworzenia i zarządzania wątkami, jest dostępna w systemach m.in. Linux, FreeBSD, Windows
- OpenMP
- ICU ( ang. Internaltional Components for Unicode) to dojrzały, szeroko stosowany zestaw bibliotek C/C++ i Java zapewniający obsługę Unicode i globalizacji dla aplikacji
- libffi - A Portable Foreign Function Interface Library - przenośny interfejs programowania wysokiego poziomu do różnych konwencji wywoływania ( języków programowania)
- libsoundio - c library, cross-platform audio input and output for real-time & consumer software
- biblioteki skłądające się z 1 pliku nagłówkowego (ang. Single-header-file C libraries)
- jit - API do osadzania GCC w programach i bibliotekach
- klib - biblioteka ogólna w C
- BoxFort - Piaskownica_(bezpieczeństwo_informatyczne, ang. sandboxing)[16]
- debugbreak - break into the debugger programmatically
- dynacall
- nanopb to implementacja C formatu danych Google Protocol Buffers. Jest przeznaczony dla 32-bitowych mikrokontrolerów, ale nadaje się również do innych systemów wbudowanych z wąskimi ograniczeniami pamięci (<10 kB ROM, <1 kB RAM).
Wg zastosowania
klib
edytujCommon 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
- ↑ http://tldp.org/HOWTO/Program-Library-HOWTO/
- ↑ stackoverflow question: linux-error-while-loading-shared-libraries-cannot-open-shared-object-file-no-s
- ↑ Programowanie funkcyjne: Modu ze strony wazniak.mimuw.edu.pl
- ↑ Programowanie funkcyjne: Modu ze strony wazniak.mimuw.edu.pl
- ↑ stackoverflow question : difference-between-static-and-shared-libraries
- ↑ Static, Shared Dynamic and Loadable Linux Libraries from yolinux
- ↑ HOWTO : shared-libraries
- ↑ drepper : how to write shared library
- ↑ Linux Libraries (Paths, Files, and Commands) by DevynCJohnson
- ↑ gcc : Search-Path
- ↑ Where Does GCC Look to Find its Header Files? by Joshua Davie
- ↑ stackoverflow :How to add a default include path for gcc in linux?
- ↑ Stackoverflow : GCC include directories
- ↑ analyzing-c-source-files-dependencies-in-a-program by balau82
- ↑ Tak naprawdę całe "ukrycie" funkcji polega na zmianie niektórych danych w pliku z kodem binarnym danej biblioteki (pliku .o), przez co linker powoduje wygenerowanie komunikatu o błędzie w czasie łączenia biblioteki z programem.
- ↑ fortinet: what-is-sandboxing ?