Co oznaczają „połączone statycznie” i „dynamicznie powiązane”?

229

Często słyszę terminy „statycznie powiązane” i „dynamicznie powiązane”, często w odniesieniu do kodu napisanego w C , C ++ lub C # . O czym oni są, o czym dokładnie rozmawiają i co łączą?

UnkwnTech
źródło

Odpowiedzi:

445

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.cplik 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.

paxdiablo
źródło
11
Popraw mnie, jeśli się mylę, ale w systemie Windows oprogramowanie zwykle dołącza własne biblioteki do instalacji, nawet jeśli są one dynamicznie połączone. W wielu systemach Linux z menedżerem pakietów wiele dynamicznie połączonych bibliotek („obiektów współdzielonych”) jest faktycznie współużytkowanych przez oprogramowanie.
Paul Fisher
6
@PaulF: takie rzeczy, jak typowe kontrolki Windows, DirectX, .NET itd., Są dostarczane z aplikacjami, podczas gdy w Linuksie zwykle używasz apt lub yum lub czegoś podobnego do zarządzania zależnościami - więc masz rację w tym sensie . Wygraj aplikacje, które dostarczają własny kod jako biblioteki DLL, zwykle nie udostępniają ich.
paxdiablo
31
W dziewiątym kręgu piekła jest specjalne miejsce zarezerwowane dla tych, którzy aktualizują swoje biblioteki DLL i łamią wsteczną kompatybilność. Tak, jeśli interfejsy znikną lub zostaną zmodyfikowane, wówczas dynamiczne łączenie spadnie na stos. Dlatego nie należy tego robić. Z całą pewnością dodaj funkcję2 () do swojej biblioteki DLL, ale nie zmieniaj funkcji (), jeśli ludzie jej używają. Najlepszym sposobem na poradzenie sobie z tym jest przekodowanie funkcji () w taki sposób, aby wywoływała funkcję2 (), ale nie zmieniała podpisu funkcji ().
paxdiablo
1
@Paul Fisher, wiem, że jest późno, ale ... biblioteka dostarczana z biblioteką DLL systemu Windows nie jest pełną biblioteką, to tylko kilka kodów pośredniczących, które mówią linkerowi, co zawiera biblioteka DLL. Linker może następnie automatycznie umieścić informacje w pliku .exe w celu załadowania biblioteki DLL, a symbole nie są wyświetlane jako niezdefiniowane.
Mark Ransom
1
@ Santropedro, pod każdym względem masz rację co do znaczenia nazw bibliotek lib, importu i DLL. Sufiks jest jedynie konwencją, więc nie czytaj w nim zbyt wiele (na przykład biblioteka DLL może mieć rozszerzenie .dlllub .sorozszerzenie) - 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.
paxdiablo
221

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 0x0000i 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 printfkod 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, printfzwykle 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.

Artelius
źródło
4
Ja też tak zrobiłem, ale wybieram tylko 1 odpowiedź.
UnkwnTech,
1
Arteliusie, szukam dogłębnie twojego wyjaśnienia na temat działania tych szalonych rzeczy na niskim poziomie. proszę odpowiedzieć, jakie książki musimy przeczytać, aby uzyskać głęboką wiedzę na temat powyższych rzeczy. Dziękuję Ci.
mahesh,
1
Niestety nie mogę zasugerować żadnych książek. Najpierw powinieneś nauczyć się języka asemblera. Wtedy Wikipedia może dać porządny przegląd takich tematów. Możesz zajrzeć do lddokumentacji GNU .
Artelius
31

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ć.

John D. Cook
źródło
16

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 filena 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 libtoollub łączeniu kodu obiektowego i bibliotek C ręcznie.

Na szczęście wiele wbudowanych bibliotek C, takich jak muslstatyczne opcje łączenia dla prawie wszystkich, jeśli nie wszystkich ich bibliotek.

Teraz straceutworzyłeś plik binarny i możesz zobaczyć, że nie ma dostępu do bibliotek przed uruchomieniem programu:

strace ./simpleprog

Teraz porównaj z wynikami stracedynamicznie połączonego programu, a zobaczysz, że wersja statycznie połączonej wersji jest znacznie krótsza!


źródło
2

(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.

sztuczny antybiotyk
źródło