Skocz do zawartości

Kompilatory od podszewki - #2 - Linker i biblioteki


Pomocna odpowiedź

Ten artykuł jest częścią serii "Kompilatory od podszewki"

JUDOhEj.thumb.png.0a2731db16b5ebb79dcb04f450d4a6de.png

Schemat procesu budowania programu. Źródło grafiki. 

W tej części

Dowiesz się czym tak właściwie są "dll'ki" - czyli znane wszystkim z systemu Windows pliki z rozszerzeniem .dll. Omówimy szerzej zastosowanie linkera i opowiemy o znaczeniu i wykorzystaniu bibliotek. Zbudujemy też między innymi własne biblioteki.

Linker

Nie przedłużając, przeanalizujmy poniższe wywołanie pracując na Windowsie:

$ clang++ -o program.exe program.cpp

Powyższe polecenie:

  1. kompiluje plik źródłowy do pliku obiektowego (ang. object file) i jednocześnie

  2. łączy w jednym kroku z innymi plikami obiektowymi i bibliotekami systemowymi w jeden plik wykonywalny

Te dwa kroki nazywamy kolejno kompilacją i linkowaniem. Etap kompilacji i linkowania można rozdzielić.

Kompilacja

$ clang++ -c program.cpp

Opcja -c wskazuje kompilatorowi, aby jedynie skompilował dany plik do pliku obiektowego. Domyślnie plik obiektowy ma taką samą nazwę jak plik źródłowy z rozszerzeniem .o (nie ma potrzeby podawania opcji -o).

Należy również nadmienić, że przy wykorzystaniu tej opcji nie jest wykonywane linkowanie z innymi plikami obiektowymi lub bibliotekami.

800px-Linker_svg.thumb.png.94804caa3bb7ddf3b09c3ef342dc753a.png

Schemat procesu linkowania. Źródło.

Linkowanie

$ clang++ -o program.exe program.o

Kompilator wie, że ma wykonać proces linkowania gdy podamy mu pliki wejściowe z rozszerzeniem .o czyli pliki obiektowe (zamiast plików źródłowych .cpp lub .c). Linker może być oddzielnym programem, np. GCC używa linkera w postaci programu ld. Co więcej pracę linkera, możemy dostosować przekazując mu dodatkowe argumenty - ma to zastosowanie np. gdy programujemy systemy wbudowane.

Kompilacja programu składającego się z wielu plików źródłowych

W przypadku, gdy program składa się z kilku plików źródłowych np. main.cpp, który korzysta z kodu znajdującego się w plikach functions.cpp i tools.cpp kompilację możemy wywołać za pomocą wywołania:

$ clang++ -o program.exe main.cpp functions.cpp tools.cpp

Czyli skompilować i zlinkować w jednym kroku. Jednak zwykle kompilujemy każdy z plików źródłowych osobno do pliku obiektowego i łączymy je razem na późniejszym etapie. W takim przypadku zmiany w jednym pliku nie wymagają ponownej kompilacji innych plików (w tym przykładzie całego programu) co przyspiesza proces kompilacji.

Przekładając to na wywołania kompilatora:

$ clang++ -c main.cpp
$ clang++ -c functions.cpp
$ clang++ -c tools.cpp
$ clang++ -o program.exe main.o functions.o tools.o

Top-5-Programming-Languages-and-their-Libraries-for-Machine-Learning-in-2020.thumb.png.e67de88d8d106a3f0b5fb2c4d0aae37f.png

Źródło grafiki.

Biblioteki

Powiedzmy, że chcielibyśmy ponownie wykorzystać część kodu z naszego poprzedniego projektu w następnym. Moglibyśmy skopiować pliki źródłowe do projektu i budować wszystko globalnie lub skopiować pliki obiektowe i linkować je z resztą programu, ale powiedzmy, że mamy ich sporo i trudno jest śledzić je wszystkie. Z pomocą przychodzą tzw. biblioteki. Są to swojego rodzaju archiwa stworzone z plików obiektowych, które można ponownie wykorzystywać.

Zapewne większość czytelników kojarzy pliki z rozszerzeniem .dll, które możemy znaleźć przeglądając katalogi z miejscami instalacji programów w systemie Windows - są to właśnie biblioteki - a w tym konkretnym przykładzie biblioteki linkowane dynamiczne.

Istnieją dwa rodzaje bibliotek - linkowane statycznie i dynamicznie. Jedną z różnic jest moment, w którym dokonywany jest proces linkowania biblioteki z programem. Biblioteki linkowanie dynamicznie nazywa się również bibliotekami współdzielonymi. Za Wikipedią:

Biblioteka statyczna (ang. static library) – rodzaj biblioteki funkcji, która łączona jest z programem w momencie konsolidacji. (...) dla systemów z rodziny Windows pliki bibliotek mają zazwyczaj rozszerzenia .lib lub .obj, natomiast dla pakietów w systemach z rodziny Unix (np. GCC) i ich portach dla systemu Microsoftu (jakim jest np. MinGW) są to .a lub .o.”

Biblioteka dynamiczna – rodzaj biblioteki, która łączona jest z programem wykonywalnym dopiero w momencie jego wykonania. Dane z bibliotek dynamicznych mogą być współdzielone przez różne programy jednocześnie. Biblioteki są ładowane do pamięci tylko raz, nawet jeśli są równocześnie współużytkowane.”

Tworzenie własnych bibliotek

Poniżej przedstawimy sposób tworzenia obu typu bibliotek korzystając z systemu Windows i kompilatora GCC.

Tworzenie bibliotek statycznych i dynamicznych z wykorzystaniem kompilatora Clang na systemach Linux i macOS znajdziesz tutaj. Natomiast z wykorzystaniem GCC na systemie Linux tutaj.

Przygotowanie

Tym razem dla odmiany w przykładzie wykorzystamy projekt napisany w języku C. Pierwszy krok to przygotowanie kodu i struktury plików. Zakładając, że jesteśmy w katalogu Forbot, musimy stworzyć następujące katalogi:

├───bin
│   ├───shared
│   └───static
└───src

W katalogu src tworzymy następujące pliki:

calculator.h

#ifndef CACULATOR_H
#define CACULATOR_H

int add(int a, int b);

#endif   // CACULATOR_H

calculator.c

#include "calculator.h"

int add(int a, int b)
{
    return a + b;
}

oraz

 printer.h

#ifndef PRINTER_H
#define PRINTER_H

void print(int a);

#endif   // PRINTER_H

printer.c

#include <stdio.h>

void print(int a)
{
    printf("The result of adding = %d\n", a);
}

wreszcie:

main.c

#include "calculator.h"
#include "printer.h"
#include <stdio.h>

int main(int argc, char* argv[])
{
    const int result = add(11, 22);
    print(result);

    printf("Press any key + enter to exit...\n");
    int a;
    scanf("%d", &a);

    return 0;
}

Uwaga: przedstawiona struktura plików i kod będzie używana w obu przypadkach - tworzenia biblioteki linkowanej dynamicznie i statycznie.

Projekt to bardzo prosty program który złożony jest z dwóch "modułów", gdzie każdy z nich znajduje się w osobnym pliku nagłówkowym i źródłowym. Ich zawartość nie ma w tym momencie większego znaczenia - istotny jest jedynie fakt, że bibliotekę można stworzyć z kodu umieszczonego w wielu plikach.

Tworzenie biblioteki statycznej

Pierwszym krokiem jest skompilowanie kodu do plików obiektowych - zarówno naszego programu czyli pliku main.c i plików, które będą składać się na tworzoną bibliotekę czyli calculator.c i printer.c.

Zakładając, że znajdujemy się w katalogu głównym - Forbot, pliki obiektowe tworzymy w opisany już wcześniej sposób (używamy opcji -c):

$ gcc -c src/main.c -o bin/main.o
$ gcc -c src/calculator.c -o bin/static/calculator.o
$ gcc -c src/printer.c -o bin/static/printer.o

Zauważ, że poszczególne wynikowe pliki obiektowe umieszczamy w konkretnych (innych) katalogach - plik obiektowy głównego programu w katalogu bin a pliki obiektowe potrzebne do biblioteki w katalogu bin/static.

Kolejnym krokiem jest stworzenie właściwej biblioteki, używamy w tym celu już nie kompilatora, ale specjalnego programu archiwizatora - ar - z opcjami rcs:

$ ar rcs bin/static/calculator_and_printer.lib bin/static/calculator.o bin/static/printer.o

Uwaga: program archiwizatora dostarczony jest wraz z kompilatorem.

Dodatkowymi argumentami są: katalog w którym ma zostać umieszczona biblioteka oraz jej nazwa - bin/static/calculator_and_printer.lib, pozostałe argumenty to ścieżki do plików obiektowych kodu, który ma tworzyć bibliotekę - bin/static/calculator.o bin/static/printer.o. Po wywołaniu tej komendy, w katalogu static stworzona zostanie biblioteka statyczna calculator_and_printer.lib.

Ostatnim krokiem, jest stworzenie programu wynikowego i statyczne zlinkowanie go z nowo utworzoną biblioteką

$ gcc bin/main.o -Lbin/static -lcalculator_and_printer -o bin/static/statically_linked_program.exe

Wywołanie ma podobną postać jak przedstawione wcześniej przy okazji opisu wykorzystania linkera. Kolejnymi argumentami są:

  • bin/main.o - plik obiektowy naszego programu,
  • -Lbin/static - opcja -L flaga wskazuje (niestandardowy) katalog, w którym można znaleźć bibliotekę czyli katalog bin/static
  • -lcalculator_and_printer - opcja -l wskazuje kompilatorowi nazwę biblioteki - zauważ, że nazwa podawana jest bez rozszerzenia .lib
  • -o bin/static/statically_linked_program.exe - to wskazanie gdzie ma zostać umieszczony program i jak ma się nazywać

W tym momencie możemy już korzystać z programu.

Utworzony plik wykonywalny nie jest zależny od żadnego innego pliku obiektowego ani biblioteki dlatego może być dystrybuowany samodzielnie bez stworzonych wcześniej plików obiektowych czy biblioteki.

Tworzenie biblioteki dynamicznej

Podobnie jak w przypadku biblioteki statycznej zaczynamy od stworzenia plików obiektowych.

Dla głównego pliku programu - main.c:

$ gcc -c src/main.c -o bin/main.o

Natomiast dla plików, które mają być częścią biblioteki:

$ gcc -c -fPIC src/calculator.c -o bin/shared/calculator.o
$ gcc -c -fPIC src/printer.c -o bin/shared/printer.o

Zauważ, że tym razem różnica nie polega jedynie na docelowym katalogu plików obiektowych, ale na dodatkowym argumencie -fPIC. Pliki obiektowe dla biblioteki współdzielonej należy skompilować z flagą -fPIC (PIC = Position Independent Code - kod niezależny od pozycji). Pliki obiektowe dla biblioteki statycznej nie potrzebują tej flagi.

Kolejnym krokiem to stworzenie biblioteki linkowanej dynamicznie - w przeciwieństwie do biblioteki statycznej - tym razem za pomocą kompilatora i opcji -shared:

$ gcc -shared bin/shared/calculator.o bin/shared/printer.o -o bin/shared/calculator_and_printer.dll

Opcja -shared wskazuje kompilatorowi, że będzie tworzyć bibliotekę współdzieloną, kolejne argumenty wywołania to podanie plików obiektowych z których zostanie utworzona biblioteka i następnie wskazanie gdzie ma zostać stworzona biblioteka i jak ma się nazywać. Zwróć uwagę na rozszerzenie pliku biblioteki - .dll - jest to rozszerzenie stosowane w systemach Windows.

Ostatnim krokiem jest stworzenie pliku uruchomieniowego programu:

$ gcc bin/main.o -Lbin/shared -lcalculator_and_printer -o bin/shared/dynamically_linked_program.exe

Ma ono taką samą postać jak w przykładzie z biblioteką linkowaną statycznie jedyne różnice wynikają z nazw katalogów i nazwy programu uruchomieniowego. Po wykonaniu tej czynności możemy już korzystać z programu. 

Uwaga: zauważ, że zarówno plik uruchomieniowy programu jak i biblioteka .dll znajdują się w tym samym katalogu. System Windows w pierwszym kroku przeszukuje lokalny katalog w poszukiwaniu plików bibliotek - dlatego najwygodniejszą opcją jest umieszczać pliki bibliotek w tym samym katalogu co program uruchomieniowy (.exe) - aby uniknąć wszelkich problemów związanych z konfliktami między plikami .dll projektu a tymi, które znajdują się już w systemie.

W przeciwieństwie do programów linkowanych statycznie, programy które korzystają z bibliotek linkowanych dynamicznie należy dystrybuować wraz z plikami bibliotek.

Przeszukaj swój komputer pod kątem katalogów z zainstalowanymi programami, z pewnością w każdym takim katalogu znajdziesz biblioteki linkowane dynamicznie.

W tej części to już wszystko

W tym momencie możesz wykorzystać zdobytą wiedzę np. do napisania prostego programu w C lub w C++ na płytkę Raspberry Pi, który będzie sterować diodą LED za pośrednictwem GPIO - skorzystaj z biblioteki WiringPi

W kolejnej części poruszymy temat automatyzacji procesu budowania projektów oraz temat kompilatorów dla innych architektur w tym systemów wbudowanych. Zajrzymy pod maskę IDE do programowania mikrokontrolerów STM32 - STM32CubeIDE. Dodatkowo przedstawimy inne przydatne narzędzia dostarczane wraz z kompilatorem.

  • Lubię! 2
Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.