Ich höre oft die Begriffe "statisch verknüpft" und "dynamisch verknüpft", oft in Bezug auf in C , C ++ geschriebenen Code oder C # geschrieben ist . Was sind sie, worüber reden sie genau und was verbinden sie?
Ich höre oft die Begriffe "statisch verknüpft" und "dynamisch verknüpft", oft in Bezug auf in C , C ++ geschriebenen Code oder C # geschrieben ist . Was sind sie, worüber reden sie genau und was verbinden sie?
Antworten:
Es gibt (in den meisten Fällen Diskontierung des interpretierten Codes) zwei Schritte, um vom Quellcode (was Sie schreiben) zum ausführbaren Code (was Sie ausführen) zu gelangen.
Die erste ist die Kompilierung, bei der Quellcode in Objektmodule umgewandelt wird.
Die zweite Verknüpfung kombiniert Objektmodule zu einer ausführbaren Datei.
Die Unterscheidung wird unter anderem dadurch getroffen, dass Bibliotheken von Drittanbietern in Ihre ausführbare Datei aufgenommen werden können, ohne dass deren Quellcode angezeigt wird (z. B. Bibliotheken für Datenbankzugriff, Netzwerkkommunikation und grafische Benutzeroberflächen), oder dass Code in verschiedenen Sprachen kompiliert werden muss (z. C und Assembler-Code zum Beispiel) und dann alle miteinander verknüpfen.
Wenn Sie statisch eine Datei einer ausführbaren Datei verknüpfen, wird der Inhalt dieser Datei zum Zeitpunkt der Verknüpfung eingeschlossen. Mit anderen Worten, der Inhalt der Datei wird physisch in die ausführbare Datei eingefügt, die Sie ausführen werden.
Wenn Sie dynamisch verknüpfen , ist ein Zeiger auf die Datei, in der verknüpft wird (z. B. der Dateiname der Datei), in der ausführbaren Datei enthalten, und der Inhalt dieser Datei ist zum Zeitpunkt der Verknüpfung nicht enthalten. Es ist nur , wenn Sie später laufen die ausführbare Datei , dass diese dynamisch verknüpften Dateien in gekauft und sie sind nur in die im Speicher befindlichen Kopie der ausführbaren Datei, nicht die auf der Festplatte gekauft.
Es ist im Grunde eine Methode der verzögerten Verknüpfung. Es gibt noch mehr latente Methode (genannt auf einigen Systemen der späten Bindung) , die in der dynamisch verknüpften Datei bringt nicht , bis Sie tatsächlich versuchen , eine Funktion innerhalb es zu nennen.
Statisch verknüpfte Dateien sind zum Zeitpunkt der Verknüpfung für die ausführbare Datei "gesperrt", sodass sie sich nie ändern. Eine dynamisch verknüpfte Datei, auf die eine ausführbare Datei verweist, kann sich ändern, indem die Datei auf der Festplatte ersetzt wird.
Dies ermöglicht Aktualisierungen der Funktionalität, ohne dass der Code erneut verknüpft werden muss. Der Loader verbindet sich jedes Mal neu, wenn Sie ihn ausführen.
Dies ist sowohl gut als auch schlecht - einerseits ermöglicht es einfachere Updates und Fehlerbehebungen, andererseits kann es dazu führen, dass Programme nicht mehr funktionieren, wenn die Updates nicht kompatibel sind - dies ist manchmal für die gefürchtete "DLL-Hölle" verantwortlich, die manche Leute haben Erwähnen Sie, dass Anwendungen beschädigt werden können, wenn Sie eine dynamisch verknüpfte Bibliothek durch eine nicht kompatible Bibliothek ersetzen (Entwickler, die dies tun, sollten übrigens damit rechnen, dass sie streng gejagt und bestraft werden).
Betrachten wir als Beispiel den Fall eines Benutzers, der seine main.c
Datei für statische und dynamische Verknüpfungen kompiliert .
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 |
+---------+ +---------+
Sie können im statischen Fall sehen, dass das Hauptprogramm und die C-Laufzeitbibliothek zur Verknüpfungszeit (von den Entwicklern) miteinander verknüpft sind. Da der Benutzer die ausführbare Datei normalerweise nicht erneut verknüpfen kann, bleibt er beim Verhalten der Bibliothek hängen.
Im dynamischen Fall ist das Hauptprogramm mit der C-Laufzeitimportbibliothek verknüpft (etwas, das deklariert, was sich in der dynamischen Bibliothek befindet, es aber nicht definiert ). Dadurch kann der Linker verknüpfen, obwohl der eigentliche Code fehlt.
Zur Laufzeit führt der Betriebssystemlader eine späte Verknüpfung des Hauptprogramms mit der C-Laufzeit-DLL (Dynamic Link Library oder Shared Library oder eine andere Nomenklatur) durch.
Der Besitzer der C-Laufzeit kann jederzeit eine neue DLL einfügen, um Updates oder Fehlerbehebungen bereitzustellen. Wie bereits erwähnt, hat dies sowohl Vor- als auch Nachteile.
.dll
oder eine .so
Erweiterung haben) - stellen Sie sich die Antwort als Erklärung der Konzepte vor, anstatt eine genaue Beschreibung zu sein. Und laut Text ist dies ein Beispiel, das statische und dynamische Verknüpfungen nur für die C-Laufzeitdateien zeigt. Ja, das ist es, was `crt in allen angibt.
Ich denke, eine gute Antwort auf diese Frage sollte erklären, was Verknüpfung ist .
Wenn Sie beispielsweise C-Code kompilieren, wird dieser in die Maschinensprache übersetzt. Nur eine Folge von Bytes, die beim Ausführen bewirkt, dass der Prozessor addiert, subtrahiert, vergleicht, "gehe", Speicher liest, Speicher schreibt, so etwas. Dieses Zeug wird in Objektdateien (.o) gespeichert.
Vor langer Zeit haben Informatiker dieses "Unterprogramm" erfunden. Führen Sie diesen Codeblock aus und geben Sie ihn hier zurück. Es dauerte nicht lange, bis ihnen klar wurde, dass die nützlichsten Unterprogramme an einem bestimmten Ort gespeichert und von jedem Programm verwendet werden konnten, das sie benötigte.
Früher mussten Programmierer die Speicheradresse eingeben, unter der sich diese Unterprogramme befanden. So etwas wie CALL 0x5A62
. Dies war mühsam und problematisch, falls diese Speicheradressen jemals geändert werden müssen.
Der Prozess wurde also automatisiert. Sie schreiben ein Programm, das aufruft printf()
, und der Compiler kennt die Speicheradresse von nicht printf
. Der Compiler schreibt also nur CALL 0x0000
und fügt der Objektdatei eine Notiz hinzu, die besagt, dass "diese 0x0000 durch den Speicherort von ersetzen muss printf ".
Statische Verknüpfung bedeutet, dass das Linkerprogramm (das GNU-Programm heißt ld ) printf
den Maschinencode direkt zu Ihrer ausführbaren Datei hinzufügt und 0x0000 in die Adresse von ändert printf
. Dies geschieht, wenn Ihre ausführbare Datei erstellt wird.
Dynamische Verknüpfung bedeutet, dass der obige Schritt nicht ausgeführt wird. Die ausführbare Datei enthält weiterhin den Hinweis "0x000 muss durch den Speicherort von printf ersetzt werden". Der Loader des Betriebssystems muss bei jedem Programmstart den printf-Code finden, in den Speicher laden und die CALL-Adresse korrigieren .
Es ist üblich, dass Programme einige Funktionen aufrufen, die statisch verknüpft werden (Standardbibliotheksfunktionen, wie sie printf
normalerweise statisch verknüpft sind), und andere Funktionen, die dynamisch verknüpft werden. Die statischen "werden Teil" der ausführbaren Datei und die dynamischen "machen mit", wenn die ausführbare Datei ausgeführt wird.
Beide Methoden haben Vor- und Nachteile, und es gibt Unterschiede zwischen den Betriebssystemen. Aber da du nicht gefragt hast, werde ich das hier beenden.
ld
Dokumentation ansehen .
Statisch verknüpfte Bibliotheken werden zur Kompilierungszeit verknüpft. Dynamisch verknüpfte Bibliotheken werden zur Laufzeit geladen. Durch statische Verknüpfung wird das Bibliotheksbit in Ihre ausführbare Datei eingebrannt. Die dynamische Verknüpfung backt nur in einem Verweis auf die Bibliothek. Die Bits für die dynamische Bibliothek sind an anderer Stelle vorhanden und können später ausgetauscht werden.
Da keiner der oben genannten Beiträge tatsächlich zeigt, wie man etwas statisch verknüpft und sieht, dass Sie es richtig gemacht haben, werde ich dieses Problem ansprechen:
Ein einfaches C-Programm
#include <stdio.h>
int main(void)
{
printf("This is a string\n");
return 0;
}
Verknüpfen Sie das C-Programm dynamisch
gcc simpleprog.c -o simpleprog
Und laufen Sie file
auf der Binärdatei:
file simpleprog
Und das wird zeigen, dass es dynamisch mit etwas verbunden ist, das wie folgt aussieht:
"simpleprog: ELF 64-Bit-LSB-ausführbare Datei, x86-64, Version 1 (SYSV), dynamisch verknüpft (verwendet gemeinsam genutzte Bibliotheken), für GNU / Linux 2.6.26, BuildID [sha1] = 0xf715572611a8b04f686809d90d1c0d75c6028f0f, nicht entfernt"
Lassen Sie uns stattdessen das Programm diesmal statisch verknüpfen:
gcc simpleprog.c -static -o simpleprog
Wenn Sie eine Datei auf dieser statisch verknüpften Binärdatei ausführen, wird Folgendes angezeigt:
file simpleprog
"simpleprog: ELF 64-Bit-LSB-ausführbare Datei, x86-64, Version 1 (GNU / Linux), statisch verknüpft, für GNU / Linux 2.6.26, BuildID [sha1] = 0x8c0b12250801c5a7c7434647b7dc65a644d6132b, nicht entfernt"
Und Sie können sehen, dass es glücklich statisch verknüpft ist. Leider sind jedoch nicht alle Bibliotheken auf diese Weise einfach statisch zu verknüpfen und erfordern möglicherweise einen längeren Aufwand bei der Verwendung libtool
oder Verknüpfung des Objektcodes und der C-Bibliotheken von Hand.
Glücklicherweise musl
bieten viele eingebettete C-Bibliotheken statische Verknüpfungsoptionen für fast alle, wenn nicht alle ihrer Bibliotheken.
Jetzt strace
die von Ihnen erstellte Binärdatei und Sie können sehen, dass vor Beginn des Programms keine Bibliotheken aufgerufen werden:
strace ./simpleprog
Vergleichen Sie nun mit der Ausgabe des strace
dynamisch verknüpften Programms und Sie werden sehen, dass die Strace der statisch verknüpften Version viel kürzer ist!
(Ich kenne C # nicht, aber es ist interessant, ein statisches Verknüpfungskonzept für eine VM-Sprache zu haben.)
Bei der dynamischen Verknüpfung müssen Sie wissen, wie Sie eine erforderliche Funktionalität finden, auf die Sie nur eine Referenz aus Ihrem Programm haben. Ihre Sprachlaufzeit oder Ihr Betriebssystem suchen im Dateisystem, im Netzwerk oder im kompilierten Code-Cache nach einem Code, der mit der Referenz übereinstimmt, und ergreifen dann verschiedene Maßnahmen, um ihn in Ihr Programmabbild im Speicher zu integrieren, z. B. das Verschieben. Sie sind alle zur Laufzeit fertig. Dies kann entweder manuell oder vom Compiler erfolgen. Es besteht die Möglichkeit, Aktualisierungen mit dem Risiko von Fehlern durchzuführen (nämlich DLL-Hölle).
Die statische Verknüpfung erfolgt zum Zeitpunkt der Kompilierung. Sie teilen dem Compiler mit, wo sich alle Funktionsteile befinden, und weisen ihn an, sie zu integrieren. Es gibt keine Suche, keine Mehrdeutigkeit, keine Möglichkeit zum Aktualisieren ohne Neukompilierung. Alle Ihre Abhängigkeiten sind physisch eins mit Ihrem Programmabbild.