Podczas tworzenia biblioteki klas w C ++ możesz wybierać między bibliotekami dynamicznymi ( .dll
, .so
) i statycznymi ( .lib
, .a
). Jaka jest różnica między nimi i kiedy należy użyć której?
Podczas tworzenia biblioteki klas w C ++ możesz wybierać między bibliotekami dynamicznymi ( .dll
, .so
) i statycznymi ( .lib
, .a
). Jaka jest różnica między nimi i kiedy należy użyć której?
Odpowiedzi:
Biblioteki statyczne zwiększają rozmiar kodu w pliku binarnym. Są zawsze ładowane, a dowolna wersja skompilowanego kodu jest wersją kodu, który zostanie uruchomiony.
Biblioteki dynamiczne są przechowywane i wersjonowane osobno. Możliwe jest załadowanie wersji biblioteki dynamicznej, która nie była oryginalną wersją dostarczoną z Twoim kodem, jeśli aktualizacja zostanie uznana za binarnie zgodną z wersją oryginalną.
Ponadto biblioteki dynamiczne niekoniecznie są ładowane - zwykle są ładowane przy pierwszym wywołaniu - i mogą być współużytkowane przez komponenty korzystające z tej samej biblioteki (wiele ładowań danych, jedno ładowanie kodu).
Biblioteki dynamiczne były przez większość czasu uważane za lepsze podejście, ale pierwotnie miały poważną wadę (piekło DLL Google), która została prawie całkowicie wyeliminowana przez nowsze systemy operacyjne Windows (w szczególności Windows XP).
Inni odpowiednio wyjaśnili, czym jest biblioteka statyczna, ale chciałbym zwrócić uwagę na niektóre zastrzeżenia dotyczące korzystania z bibliotek statycznych, przynajmniej w systemie Windows:
Singletony: Jeśli coś musi być globalne / statyczne i niepowtarzalne, należy bardzo uważać na umieszczenie go w bibliotece statycznej. Jeśli wiele bibliotek DLL jest połączonych z tą statyczną biblioteką, każda z nich otrzyma własną kopię singletonu. Jeśli jednak twoja aplikacja to pojedynczy plik EXE bez niestandardowych bibliotek DLL, może to nie stanowić problemu.
Usuwanie kodu bez odniesień: Podczas łączenia z biblioteką statyczną tylko części biblioteki statycznej, do których odwołuje się biblioteka DLL / EXE, zostaną połączone z biblioteką DLL / EXE.
Na przykład, jeśli mylib.lib
zawiera, a.obj
a b.obj
twoja biblioteka DLL / EXE odwołuje się tylko do funkcji lub zmiennych a.obj
, całość b.obj
zostanie odrzucona przez linker. Jeśli b.obj
zawiera obiekty globalne / statyczne, ich konstruktory i destruktory nie zostaną wykonane. Jeśli ci konstruktorzy / destruktory mają skutki uboczne, możesz być rozczarowany ich nieobecnością.
Podobnie, jeśli biblioteka statyczna zawiera specjalne punkty wejścia, może być konieczne zadbanie o to, aby były one uwzględnione. Przykładem tego w programowaniu wbudowanym (w porządku, nie w systemie Windows) może być moduł obsługi przerwań oznaczony jako znajdujący się pod określonym adresem. Musisz także zaznaczyć moduł obsługi przerwań jako punkt wejścia, aby upewnić się, że nie zostanie odrzucony.
Inną konsekwencją tego jest to, że biblioteka statyczna może zawierać pliki obiektowe, które są całkowicie bezużyteczne z powodu nierozwiązanych odwołań, ale nie spowoduje to błędu linkera, dopóki nie odwołasz się do funkcji lub zmiennej z tych plików obiektowych. Może się to zdarzyć długo po napisaniu biblioteki.
Symbole debugowania: Możesz chcieć osobnego PDB dla każdej biblioteki statycznej lub możesz chcieć, aby symbole debugowania były umieszczane w plikach obiektowych, aby były one wtaczane do PDB dla DLL / EXE. Dokumentacja Visual C ++ wyjaśnia niezbędne opcje .
RTTI: Możesz skończyć z wieloma type_info
obiektami dla tej samej klasy, jeśli połączysz jedną bibliotekę statyczną z wieloma bibliotekami DLL. Jeśli twój program zakłada, że type_info
są to dane „singleton” i wykorzystuje &typeid()
lub type_info::before()
, możesz uzyskać niepożądane i zaskakujące wyniki.
Lib to jednostka kodu zawarta w pliku wykonywalnym aplikacji.
Dll jest samodzielną jednostką kodu wykonywalnego. Jest on ładowany w procesie tylko wtedy, gdy do tego kodu zostanie wywołane wywołanie. Biblioteka DLL może być używana przez wiele aplikacji i ładowana w wielu procesach, a jednocześnie ma tylko jedną kopię kodu na dysku twardym.
Dll pros : może być użyty do ponownego użycia / współdzielenia kodu między kilkoma produktami; ładowanie do pamięci procesu na żądanie i może być rozładowane, gdy nie jest potrzebne; można zaktualizować niezależnie od reszty programu.
Wady dll : wpływ na ładowanie dll i zmiany kodu; problemy z wersjonowaniem („dll hell”)
Lib plus : brak wpływu na wydajność, ponieważ kod jest zawsze ładowany w procesie i nie jest ponownie bazowany; brak problemów z wersjonowaniem.
Lib Cons : plik wykonywalny / proces „wzdęcia” - cały kod znajduje się w pliku wykonywalnym i jest ładowany przy uruchomieniu procesu; bez ponownego użycia / udostępniania - każdy produkt ma własną kopię kodu.
Oprócz technicznych implikacji bibliotek statycznych vs. dynamicznych (pliki statyczne zawierają wszystko w jednej dużej bibliotece binarnej vs. dynamicznej, które umożliwiają współdzielenie kodu między kilkoma różnymi plikami wykonywalnymi), istnieją implikacje prawne .
Na przykład, jeśli korzystasz z kodu licencjonowanego LGPL i łączysz się statycznie z biblioteką LGPL (i w ten sposób tworzysz jeden duży plik binarny), Twój kod automatycznie staje się Open Sourced ( darmowy jak na wolności) kodem LGPL typu . Jeśli łączysz się z obiektami współużytkowanymi, musisz tylko LGPL ulepszeń / poprawek błędów, które wprowadziłeś do samej biblioteki LGPL.
Staje się to o wiele ważniejszym problemem, jeśli decydujesz na przykład, jak skompilować swoje aplikacje mobilne (w Androidzie masz wybór statyczny vs dynamiczny, w iOS nie - zawsze jest statyczny).
Programy C ++ są zbudowane w dwóch fazach
Biblioteka statyczna (.lib) jest tylko pakietem plików .obj i dlatego nie jest kompletnym programem. Nie przeszedł on drugiej (łączącej) fazy budowania programu. Z drugiej strony biblioteki DLL są podobne do plików exe i dlatego są kompletnymi programami.
Jeśli zbudujesz bibliotekę statyczną, nie jest ona jeszcze połączona i dlatego konsumenci twojej biblioteki statycznej będą musieli użyć tego samego kompilatora, którego użyłeś (jeśli użyłeś g ++, będą musieli użyć g ++).
Jeśli zamiast tego zbudowałeś bibliotekę DLL (i poprawnie ją zbudowałeś ), zbudowałeś kompletny program, z którego mogą korzystać wszyscy konsumenci, bez względu na to, jakiego kompilatora używają. Istnieje jednak kilka ograniczeń dotyczących eksportu z biblioteki dll, jeśli wymagana jest kompatybilność z wieloma kompilatorami.
consumers of your static library will have to use the same compiler that you used
jeśli biblioteka statyczna korzysta z biblioteki C ++, takiej jak #include <iostream>
.
$$:~/static [32]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/static [33]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/static [34]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/static [35]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/static [36]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/static [37]> cat makefile
hello: hello.o libtest.a
cc -o hello hello.o -L. -ltest
hello.o: hello.c
cc -c hello.c -I`pwd`
libtest.a:foo.o foo2.o
ar cr libtest.a foo.o foo2.o
foo.o:foo.c
cc -c foo.c
foo2.o:foo.c
cc -c foo2.c
clean:
rm -f foo.o foo2.o libtest.a hello.o
$$:~/static [38]>
$$:~/dynamic [44]> cat foo.c
#include<stdio.h>
void foo()
{
printf("\nhello world\n");
}
$$:~/dynamic [45]> cat foo.h
#ifndef _H_FOO_H
#define _H_FOO_H
void foo();
#endif
$$:~/dynamic [46]> cat foo2.c
#include<stdio.h>
void foo2()
{
printf("\nworld\n");
}
$$:~/dynamic [47]> cat foo2.h
#ifndef _H_FOO2_H
#define _H_FOO2_H
void foo2();
#endif
$$:~/dynamic [48]> cat hello.c
#include<foo.h>
#include<foo2.h>
void main()
{
foo();
foo2();
}
$$:~/dynamic [49]> cat makefile
hello:hello.o libtest.sl
cc -o hello hello.o -L`pwd` -ltest
hello.o:
cc -c -b hello.c -I`pwd`
libtest.sl:foo.o foo2.o
cc -G -b -o libtest.sl foo.o foo2.o
foo.o:foo.c
cc -c -b foo.c
foo2.o:foo.c
cc -c -b foo2.c
clean:
rm -f libtest.sl foo.o foo
2.o hello.o
$$:~/dynamic [50]>
Biblioteka statyczna jest kompilowana do klienta. Plik .lib jest używany podczas kompilacji, a zawartość biblioteki staje się częścią konsumującego pliku wykonywalnego.
Biblioteka dynamiczna jest ładowana w czasie wykonywania i nie jest kompilowana w pliku wykonywalnym klienta. Biblioteki dynamiczne są bardziej elastyczne, ponieważ wiele plików wykonywalnych klienta może załadować bibliotekę DLL i wykorzystać jej funkcjonalność. Dzięki temu ogólny rozmiar i łatwość konserwacji kodu klienta są minimalne.
Powinieneś dokładnie przemyśleć zmiany w czasie, wersjonowanie, stabilność, kompatybilność itp.
Jeśli istnieją dwie aplikacje korzystające ze wspólnego kodu, czy chcesz zmusić te aplikacje do zmiany razem, na wypadek, gdyby musiały być ze sobą kompatybilne? Następnie użyj biblioteki dll. Wszystkie pliki exe będą używać tego samego kodu.
A może chcesz je oddzielić od siebie, abyś mógł je zmienić i mieć pewność, że nie złamiesz drugiego. Następnie użyj statycznej biblioteki lib.
Piekło DLL ma miejsce wtedy, gdy prawdopodobnie POWINIENEŚ użyć statycznej biblioteki lib, ale zamiast tego użyłeś biblioteki dll i nie wszystkie exy są z nią kompatybilne.
Biblioteka statyczna musi być połączona z końcowym plikiem wykonywalnym; staje się częścią pliku wykonywalnego i podąża za nim, gdziekolwiek się znajduje. Biblioteka dynamiczna jest ładowana za każdym razem, gdy plik wykonywalny jest wykonywany i pozostaje oddzielna od pliku wykonywalnego jako plik DLL.
Korzystasz z biblioteki DLL, jeśli chcesz mieć możliwość zmiany funkcjonalności dostarczanej przez bibliotekę bez konieczności ponownego łączenia pliku wykonywalnego (wystarczy zastąpić plik DLL, bez konieczności zastępowania pliku wykonywalnego).
Korzystasz z biblioteki statycznej, gdy nie masz powodu, aby używać biblioteki dynamicznej.
Artykuł Ulricha Dreppera na temat „ Jak pisać biblioteki współdzielone ” jest również dobrym zasobem, który szczegółowo opisuje, jak najlepiej korzystać z bibliotek współdzielonych lub co określa jako „dynamiczne obiekty współdzielone” (DSO). Koncentruje się bardziej na bibliotekach współdzielonych w formacie binarnym ELF , ale niektóre dyskusje są odpowiednie również dla bibliotek DLL systemu Windows.
Aby uzyskać doskonałą dyskusję na ten temat, przeczytaj ten artykuł od Sun.
Ma wszystkie zalety, w tym możliwość wstawiania bibliotek wstawiających. Więcej szczegółów na temat interpozycji można znaleźć w tym artykule tutaj .
Naprawdę kompromis, który podejmujesz (w dużym projekcie), polega na początkowym czasie ładowania, biblioteki będą się łączyły w tym czy innym czasie, decyzja, którą należy podjąć, to czy połączenie zajmie wystarczająco dużo czasu, którego potrzebuje kompilator ugryźć pocisk i zrobić to z góry, lub dynamiczny linker może to zrobić w czasie ładowania.
Jeśli twoja biblioteka ma być współużytkowana przez kilka plików wykonywalnych, często warto nadać jej dynamiczny charakter, aby zmniejszyć rozmiar plików wykonywalnych. W przeciwnym razie zdecydowanie uczyń go statycznym.
Istnieje kilka wad korzystania z biblioteki dll. Dodatkowe obciążenie związane jest z załadunkiem i rozładunkiem. Istnieje również dodatkowa zależność. Jeśli zmienisz bibliotekę DLL, aby była niezgodna z plikami wykonywalnymi, przestaną działać. Z drugiej strony, jeśli zmienisz bibliotekę statyczną, nie wpłynie to na skompilowane pliki wykonywalne przy użyciu starej wersji.
Jeśli biblioteka jest statyczna, to w czasie łączenia kod jest łączony z plikiem wykonywalnym. Dzięki temu plik wykonywalny jest większy (niż gdybyś wybrał ścieżkę dynamiczną).
Jeśli biblioteka jest dynamiczna, wówczas w czasie łączenia w pliku wykonywalnym są wbudowane odwołania do wymaganych metod. Oznacza to, że musisz wysłać plik wykonywalny i bibliotekę dynamiczną. Należy również rozważyć, czy współużytkowany dostęp do kodu w bibliotece jest bezpieczny, preferowany adres ładowania między innymi.
Jeśli możesz żyć z biblioteką statyczną, przejdź do biblioteki statycznej.
W naszym projekcie używamy wielu bibliotek DLL (> 100). Te biblioteki DLL są od siebie zależne, dlatego wybraliśmy konfigurację dynamicznego łączenia. Ma jednak następujące wady:
Być może lepszą konfiguracją było uczynienie wszystkiego statyczną biblioteką (a zatem masz tylko jeden plik wykonywalny). Działa to tylko wtedy, gdy nie ma miejsca powielanie kodu. Test wydaje się potwierdzać to założenie, ale nie mogłem znaleźć oficjalnej wyceny MSDN. Na przykład zrób 1 exe z:
Kod i zmienne shared_lib2 powinny być obecne w końcowym scalonym pliku wykonywalnym tylko raz. Czy ktoś może poprzeć to pytanie?
Biblioteki statyczne to archiwa zawierające kod obiektowy biblioteki, gdy są połączone z aplikacją, kod ten jest wkompilowany w plik wykonywalny. Biblioteki współdzielone różnią się tym, że nie są wkompilowane w plik wykonywalny. Zamiast tego dynamiczny linker przeszukuje niektóre katalogi w poszukiwaniu potrzebnych bibliotek, a następnie ładuje je do pamięci. Więcej niż jeden plik wykonywalny może jednocześnie korzystać z tej samej biblioteki współużytkowanej, co zmniejsza zużycie pamięci i rozmiar pliku wykonywalnego. Istnieje jednak więcej plików do rozpowszechnienia w pliku wykonywalnym. Musisz upewnić się, że biblioteka jest zainstalowana w systemie użytkownika w miejscu, w którym linker może ją znaleźć, statyczne łączenie eliminuje ten problem, ale skutkuje większym plikiem wykonywalnym.
Jeśli pracujesz nad projektami osadzonymi lub na wyspecjalizowanych platformach, biblioteki statyczne są jedyną drogą, którą możesz wykonać, również wiele razy mniej kłopotów z kompilacją w twojej aplikacji. Posiadanie projektów i plików mak, które zawierają wszystko, sprawia, że życie jest szczęśliwsze.
Podałbym ogólną zasadę, że jeśli masz dużą bazę kodu, wszystkie zbudowane na bibliotekach niższego poziomu (np. Frameworku Utils lub Gui), które chcesz podzielić na łatwiejsze do zarządzania biblioteki, a następnie uczyń je bibliotekami statycznymi. Biblioteki dynamiczne tak naprawdę nic nie kupują i jest mniej niespodzianek - na przykład będzie tylko jedna instancja singletonów.
Jeśli masz bibliotekę, która jest całkowicie oddzielna od reszty bazy kodu (np. Biblioteka strony trzeciej), rozważ utworzenie dll. Jeśli biblioteką jest LGPL, może być konieczne użycie biblioteki DLL ze względu na warunki licencyjne.