Odpowiedzi:
Istnieją (w większości przypadków pomijanie interpretowanego kodu) dwa etapy przechodzenia od kodu źródłowego (co piszesz) do kodu wykonywalnego (co uruchamiasz).
Pierwszą jest kompilacja, która zamienia kod źródłowy w moduły obiektowe.
Drugi, łączący, łączy moduły obiektowe w jeden plik wykonywalny.
Rozróżnia się między innymi umożliwienie dołączania bibliotek osób trzecich do pliku wykonywalnego bez oglądania ich kodu źródłowego (takiego jak biblioteki dostępu do bazy danych, komunikacji sieciowej i graficznych interfejsów użytkownika) lub do kompilowania kodu w różnych językach ( Na przykład C i kod asemblera), a następnie łącząc je wszystkie razem.
Kiedy statycznie łączysz plik z plikiem wykonywalnym, zawartość tego pliku jest uwzględniana w czasie łączenia. Innymi słowy, zawartość pliku jest fizycznie wstawiana do pliku wykonywalnego, który uruchomisz.
Gdy łączysz się dynamicznie , wskaźnik do pliku, do którego plik jest podłączony (na przykład nazwa pliku), jest dołączany do pliku wykonywalnego, a zawartość tego pliku nie jest uwzględniana w czasie łączenia. Tylko wtedy, gdy później uruchomisz plik wykonywalny, te dynamicznie połączone pliki są kupowane i są kupowane tylko w kopii pliku wykonywalnego w pamięci, a nie na dysku.
Jest to w zasadzie metoda odroczonego łączenia. Istnieje jeszcze bardziej odroczona metoda (zwana późnym wiązaniem w niektórych systemach), która nie wprowadzi dynamicznie połączonego pliku, dopóki nie spróbujesz wywołać w nim funkcji.
Pliki połączone statycznie są „blokowane” w pliku wykonywalnym w czasie łącza, więc nigdy się nie zmieniają. Dynamicznie połączony plik, do którego odwołuje się plik wykonywalny, można zmienić, po prostu zastępując plik na dysku.
Umożliwia to aktualizacje funkcjonalności bez konieczności ponownego łączenia kodu; moduł ładujący łączy się ponownie przy każdym uruchomieniu.
Jest to zarówno dobre, jak i złe - z jednej strony pozwala na łatwiejsze aktualizacje i poprawki błędów, z drugiej strony może prowadzić do zaprzestania działania programów, jeśli aktualizacje nie są kompatybilne - czasami jest to odpowiedzialne za przerażające „piekło DLL”, które niektórzy ludzie wspomnij, że aplikacje mogą zostać zepsute, jeśli zamienisz dynamicznie połączoną bibliotekę na bibliotekę, która nie jest kompatybilna (programiści, którzy to robią, powinni się spodziewać, że zostaną ścigani i surowo ukarani).
Jako przykład przyjrzyjmy się sytuacji, w której użytkownik kompiluje swój main.c
plik do łączenia statycznego i dynamicznego.
Phase Static Dynamic
-------- ---------------------- ------------------------
+---------+ +---------+
| main.c | | main.c |
+---------+ +---------+
Compile........|.........................|...................
+---------+ +---------+ +---------+ +--------+
| main.o | | crtlib | | main.o | | crtimp |
+---------+ +---------+ +---------+ +--------+
Link...........|..........|..............|...........|.......
| | +-----------+
| | |
+---------+ | +---------+ +--------+
| main |-----+ | main | | crtdll |
+---------+ +---------+ +--------+
Load/Run.......|.........................|..........|........
+---------+ +---------+ |
| main in | | main in |-----+
| memory | | memory |
+---------+ +---------+
W przypadku statycznym widać, że program główny i biblioteka środowiska wykonawczego C są połączone razem w czasie łączenia (przez programistów). Ponieważ użytkownik zwykle nie może ponownie połączyć pliku wykonywalnego, utknął w zachowaniu biblioteki.
W przypadku dynamicznym program główny jest połączony z biblioteką importu środowiska wykonawczego C (coś, co deklaruje zawartość biblioteki dynamicznej, ale tak naprawdę jej nie definiuje ). Umożliwia to linkerowi połączenie, mimo że brakuje rzeczywistego kodu.
Następnie w czasie wykonywania moduł ładujący system operacyjny późno łączy główny program z biblioteką DLL środowiska wykonawczego C (biblioteka łączy dynamicznych lub biblioteka współdzielona lub inna nomenklatura).
Właściciel środowiska wykonawczego C może w dowolnym momencie dodać nową bibliotekę DLL, aby zapewnić aktualizacje lub poprawki błędów. Jak wspomniano wcześniej, ma to zarówno zalety, jak i wady.
.dll
lub .so
rozszerzenie) - pomyśl o odpowiedzi jako o wyjaśnieniu pojęć, a nie o dokładnym opisie. I, tak jak w tekście, jest to przykład pokazujący statyczne i dynamiczne łączenie tylko plików środowiska wykonawczego C, więc tak, to właśnie wskazuje `crt we wszystkich z nich.
Myślę, że dobra odpowiedź na to pytanie powinna wyjaśniać, czym jest łączenie .
Kiedy kompilujesz jakiś kod C (na przykład), jest on tłumaczony na język maszynowy. Tylko sekwencja bajtów, która po uruchomieniu powoduje, że procesor dodaje, odejmuje, porównuje, „goto”, odczytuje pamięć, zapisuje pamięć itp. Te rzeczy są przechowywane w plikach obiektowych (.o).
Dawno temu informatycy wymyślili to „podprogram”. Wykonaj-to-fragment-kodu-i-zwróć tutaj. Nie trwało długo, zanim zdali sobie sprawę, że najbardziej przydatne podprogramy mogą być przechowywane w specjalnym miejscu i używane przez każdy program, który ich potrzebuje.
Teraz na początku programiści musieliby wpisać adres pamięci, w którym te procedury były zlokalizowane. Coś jak CALL 0x5A62
. Było to uciążliwe i problematyczne, gdyby adresy pamięci kiedykolwiek musiały zostać zmienione.
Tak więc proces został zautomatyzowany. Piszesz program, który wywołuje printf()
, a kompilator nie zna adresu pamięci printf
. Tak więc kompilator po prostu zapisuje CALL 0x0000
i dodaje notatkę do pliku obiektowego, mówiąc: „należy zastąpić ten 0x0000 miejscem pamięci printf ”.
Łączenie statyczne oznacza, że program łączący (GNU nazywa się ld ) dodaje printf
kod maszynowy bezpośrednio do pliku wykonywalnego i zmienia 0x0000 na adres printf
. Dzieje się tak, gdy tworzony jest plik wykonywalny.
Połączenie dynamiczne oznacza, że powyższy krok nie nastąpi. Plik wykonywalny nadal zawiera notatkę z informacją: „należy zastąpić 0x000 miejscem pamięci printf”. Moduł ładujący systemu operacyjnego musi znaleźć kod printf, załadować go do pamięci i poprawić adres CALL przy każdym uruchomieniu programu .
Programy często wywołują niektóre funkcje, które zostaną połączone statycznie (takie jak standardowe funkcje biblioteki, printf
zwykle są statycznie połączone) oraz inne funkcje, które są połączone dynamicznie. Te statyczne „stają się częścią” pliku wykonywalnego, a dynamiczne „łączą się”, gdy plik wykonywalny jest uruchamiany.
Obie metody mają zalety i wady oraz różnice między systemami operacyjnymi. Ale skoro nie zapytałeś, zakończę to tutaj.
ld
dokumentacji GNU .
Biblioteki połączone statycznie są połączone podczas kompilacji. Dynamicznie połączone biblioteki są ładowane w czasie wykonywania. Łączenie statyczne wprawia bibliotekę w plik wykonywalny. Łączenie dynamiczne piecze tylko w odniesieniu do biblioteki; bity dla biblioteki dynamicznej istnieją gdzie indziej i można je później zamienić.
Ponieważ żaden z powyższych postów tak naprawdę nie pokazuje, jak statycznie połączyć coś i sprawdzić, czy zrobiłeś to poprawnie, więc rozwiążę ten problem:
Prosty program C.
#include <stdio.h>
int main(void)
{
printf("This is a string\n");
return 0;
}
Dynamicznie połącz program C.
gcc simpleprog.c -o simpleprog
I uruchom file
na pliku binarnym:
file simpleprog
A to pokaże, że jest dynamicznie powiązane coś w następujący sposób:
„simpleprog: ELF 64-bitowy plik wykonywalny LSB, x86-64, wersja 1 (SYSV), dynamicznie połączony (wykorzystuje współdzielone biblioteki lib), dla GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, bez pasków”
Zamiast tego pozwólmy tym razem statycznie połączyć program:
gcc simpleprog.c -static -o simpleprog
Uruchomienie pliku na tym statycznie połączonym pliku binarnym pokaże:
file simpleprog
„simpleprog: ELF 64-bitowy plik wykonywalny LSB, x86-64, wersja 1 (GNU / Linux), statycznie powiązany, dla GNU / Linux 2.6.26, BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b, bez pasków”
I widać, że jest on szczęśliwie powiązany statycznie. Niestety nie wszystkie biblioteki można w prosty sposób połączyć statycznie w ten sposób i może to wymagać dłuższego wysiłku przy użyciu libtool
lub łączeniu kodu obiektowego i bibliotek C ręcznie.
Na szczęście wiele wbudowanych bibliotek C, takich jak musl
statyczne opcje łączenia dla prawie wszystkich, jeśli nie wszystkich ich bibliotek.
Teraz strace
utworzyłeś plik binarny i możesz zobaczyć, że nie ma dostępu do bibliotek przed uruchomieniem programu:
strace ./simpleprog
Teraz porównaj z wynikami strace
dynamicznie połączonego programu, a zobaczysz, że wersja statycznie połączonej wersji jest znacznie krótsza!
(Nie znam C #, ale interesująca jest statyczna koncepcja łączenia dla języka VM)
Dynamiczne łączenie polega na tym, jak znaleźć wymaganą funkcjonalność, do której masz odniesienie tylko z twojego programu. Środowisko wykonawcze lub system operacyjny języka wyszukuje fragment kodu w pamięci podręcznej systemu plików, sieci lub skompilowanego kodu, dopasowując odwołanie, a następnie podejmuje szereg działań w celu zintegrowania go z obrazem programu w pamięci, np. Przeniesienia. Wszystkie są wykonywane w czasie wykonywania. Można to zrobić ręcznie lub za pomocą kompilatora. Istnieje możliwość aktualizacji z ryzykiem zepsucia (a mianowicie, piekła DLL).
Łączenie statyczne odbywa się w czasie kompilacji, gdy informujesz kompilator, gdzie znajdują się wszystkie części funkcjonalne i instruujesz go, aby je zintegrował. Nie ma wyszukiwania, niejednoznaczności, nie ma możliwości aktualizacji bez ponownej kompilacji. Wszystkie twoje zależności są fizycznie jednością z obrazem programu.