Statisches Linken
  
  
  
  - Einleitung
 
  - Statisches vs. dynamisches Linken
 
  - Vorteile und Nachteile
 
  - Manuelles statisches Linken in C und C++
    
      - Windows mit Visual C++ MSVC
        
          - Statische Bibliothek erstellen und in EXE linken
 
          - C Runtime statisch linken
 
        
       
      - Linux mit GCC
        
          - Statische Bibliothek erstellen und in ein Executable linken
 
          - C Runtime statisch linken
 
        
       
    
   
  - Statisches Linken in CMAKE
    
      - Ausführbares Programm in CMake gegen statischen Libs linken lassen
 
      - C-Runtime in CMake statisch linken lassen
 
    
   
  - Statisches Linken in CONAN
    
      - Statische Bibliothek mit CONAN erstellen
 
      - Statische Bibliothek mit CONAN in EXE linken
 
    
   
  - Bekannte Problemfälle
 
  - Weiterführende Links
 
 Einleitung
Der Begriff der “statisch-gelinkten-Bibliotheken” (static libraries 
genannt) ist meines Erachtens nur entstanden, um ein Gegenteil zu 
“dynamisch-gelinkten-Bibliotheken” (auch als shared libraries bekannt) 
zu schaffen.
Denn eigentlich war vor der Ära der Betriebssysteme quasi alles “statisch”
gelinkt, oder anders gesagt, ein Programm bestand aus genau einem Code-Modul,
welches die gesamte Funktionalität beinhaltete.
Auf Mikrocontrollern wird auch heute in erster Linie statisch kompiliert.
Und nachdem dieses Thema gerne zum Diskussionspunkt zwischen “gutem” und
“schlechten” Programmierstil wird, möchte ich hier mal alle meine Erkenntnisse
zusammentragen.
 Statisches vs. dynamisches Linken
Im klassischen C und C++, wie auch in vielen anderen Nicht-Interpreter 
Sprachen werden Quellcodes in Maschinencode übersetzt und Funktionen wie
auch Variablen erhalten beim Kompilieren vorerst Platzhalter für die
Adressen, an denen sie operieren sollen.
Der Linker hat dann nach dem Kompilieren die Aufgabe, die einzelnen 
Programmteile in eine Binärdatei zusammenzufassen und alle Platzhalter
durch Adressen zu ersetzen, damit der Prozessor sie beim Ausführen 
anspringen kann.
Beim statischen Kompilieren passiert genau das und wir erhalten am Ende
eine Binärdatei (unter Windows: eine EXE) mit dem gesamten Programmcode.
Und auf Mikrocontrollern war und ist das die primäre Art der Kompilierung.
Moderne Betriebssysteme erlauben uns allerdings Code auch “dynamisch” zu
linken. Es können dabei mehrere Bibliotheksdateien entstehen (unter Windows 
*.dll und unter Linux *.so), die vom Hauptprogramm “dynamisch” eingebunden
werden.
Das Programm wird dabei mit Verweisen versehen, dass Code-Teile in anderen
Bibliotheken implementiert sind und entweder kümmert sich das Betriebssystem
darum, dass diese “dynamischen” Bibliotheken mit dem Programm mitgeladen 
werden, oder das Programm leitet die notwendigen Schritte selbst ein.
Beispiel: 1 Programm + 2 dynamische Libs = 3 finale Binärdateien
graph TD
  subgraph "files.dll / libfiles.so"
    dyn_open[[open]]
    dyn_read[[read]]
    dyn_write[[write]]
    dyn_close[[close]]
  end
  subgraph "Program executable importing shared libraries at runtime"
    main(main)
  end
  subgraph "math.dll / libmath.so"
    dyn_sin[[sin]]
    dyn_cos[[cos]]
    dyn_tan[[tan]]
  end
  main --import--- dyn_sin
  main --import--- dyn_cos
  main --import--- dyn_tan
  main --import--- dyn_open
  main --import--- dyn_read
  main --import--- dyn_write
  main --import--- dyn_close
  main --API
call--- dyn_sin
  main --API
call--- dyn_cos
  main --API
call--- dyn_tan
  main --API
call--- dyn_open
  main --API
call--- dyn_read
  main --API
call--- dyn_write
  main --API
call--- dyn_close
Beispiel: 1 Programm + 2 statische Libs = 1 finale Binärdatei
graph TD
  subgraph "Program executable including static libraries at buildtime"
   
    subgraph "static 'math' lib"
      static_sin[[sin]]
      static_cos[[cos]]
      static_tan[[tan]]
    end
    subgraph "static 'files' lib"
      static_open[[open]]
      static_read[[read]]
      static_write[[write]]
      static_close[[close]]
    end
    static_main(main)
    static_sin --local
call--- static_main
    static_cos --local
call--- static_main
    static_tan --local
call--- static_main
    
    static_open --local
call--- static_main
    static_read --local
call--- static_main
    static_write --local
call--- static_main
    static_close --local
call--- static_main
  end
 Vorteile und Nachteile
Dynamische Bibliotheken wurden geschaffen, damit mehrere Programme nicht
stets Kopien der gleichen Algorithmen und Funktionen in sich tragen müssen.
Eine dynamische Bibliothek exportiert also häufig genutzte Funktionen
und mehrere Programme können dann auf die gleiche Bibliothek verweisen und
ihren Code gemeinsam nutzen.
Das spart einerseits Speicherplatz und wenn eine Bibliothek einen Fehler
aufweist, kann man diesen an einer Stelle fixen womit alle Programme den
Bugfix erhalten, ohne selbst angepasst werden zu müssen.
Vor allem Betriebssysteme stellen ihre eigenen Funktionen deshalb gerne als
dynamische bzw. gemeinsam-genutzte Bibliotheken (== shared library) zur 
Verfügung.
Würde man eine Vielzahl von Programmen erstellen, die alle im gleichen
Umfeld arbeiten, wären dynamische Libs von Vorteil, wo jede Funktionsgruppe
eine Bibliothek bildet und die Programme dann recht klein sind und nur
noch die Bibliotheksfunktionen mit etwas Logik zusammenziehen.
Wären die Programme mit statischen Bibliotheken gebaut, wären sie wesentlich
größer, da sie alle Kopien der statischen Funktionen in sich tragen.
So kann ein statisches Projekt gerne auch einige Megabytes größer ausfallen,
als mit dynamischen Libs.
  Warum sollte man es also bei eigenen Programmen anders machen
und statische Libs nutzen?
Dynamische Bibliotheken müssen ein festes API
definieren, also eine binäre Schnittstelle, die zwischen Programm und 
Bibliothek vereinbart ist, und die sich nicht ändern darf, ansonsten kommt
es zu Korruption, Abstürzen oder Schlimmerem.
Auch der Compiler muss sich exakt 
an diese Schnittstelle halten, und leider verhindert genau das die Anwendung
von Optimierungen.
Bei statischen Bibliotheken kann der Compiler bei aktivierter 
Link-Time-Optimization 
(LTO, unter MSVC auch Whole-Program-Optimization oder WPO genannt)
am Ende in alle Funktionen hineinsehen und viele Optimierungen 
durchführen, die durch ein “fixiertes dynamisches API” verhindert werden.
Beispiel:
   1 // get_setting.c
 2 int get_setting()
 3 {
 4   return 42;
 5 }
 6
 7 // main_prog.c
 8 int main()
 9 {
10   printf("%d", get_setting());
11   return 0;
12 }
 
 
 
Dynamisch gelinkt, muss die Funktion get_setting() eine echte Funktion 
sein und der Compiler muss Code für den Aufruf und die Rückgabe generieren.
Statisch gelinkt, erkennt der Compiler die Konstante und kann so tun, als
hätten wir printf("%d", 42); oder sogar nur printf("42"); geschrieben.
Und das ist wesentlich schneller und kann auch weniger Speicherplatz erfordern.
Ein anderer Vorteil ist, dass das Deployment einer einzigen größeren 
“All-in-one-EXE” viel einfacher ausfallen kann, als mit vielen kleinen 
.dll oder .so Dateien nebst der ausführbaren Programmdatei.
Ich ziehe daher im Embedded-Bereich statisch kompilierte “All-in-one-Programme”
einer Fülle von kleinen Bibliotheken stets vor, weil man da nicht versehentlich
vergessen kann, eine der vielen dynamischen Libs mitzuliefern.
Letztendlich entscheidet aber der Anwendungsfall, wie effizient welche Methode
am Ende ist.
 Manuelles statisches Linken in C und C++
Grundsätzlich teilt man dem Compiler und dem Linker per 
Kommandozeilenparameter mit, ob aus einer Bibliothek eine statische oder 
dynamische werden soll und ähnliches geschieht auch bei dem finalen Programm,
wenn es entweder die eine oder andere Variante von Bibliotheken nutzen soll.
Hinweis: Auch die C-Runtime ist eine Bibliothek und kann statisch
und dynamisch gelinkt werden.
 Windows mit Visual C++ MSVC
Statische Bibliotheken haben beim MSVC
die Dateierweiterung .lib und sind quasi eine Zusammenfassung aller .obj 
Dateien, die beim Kompilieren einzelner .c und .cpp Dateien entstehen.
In der Visual Studio IDE entscheidet man in den Projekt-Einstellungen per
Menü, was man haben möchte und dann werden alle Dateien in dem Projekt
bereits mit den korrekten Optionen gebaut.

Will man eine statische Bibliothek selbst anlegen, muss man all .c und 
.cpp Dateien mit cl.exe /c zu Objektdateien kompilieren und dann
per lib.exe die entstandenen .obj Dateien in eine  .lib Datei verwandeln.
Falls man nicht den /Fo: Parameter setzt um den Namen der Objektdatei 
manuell zu bestimmen, erhält diese immer den Basisnamen der Ursprungsdatei.
z.B.: my_file.c wird zu my_file.obj
 Statische Bibliothek erstellen und in EXE linken
  1 cl /c my_static_lib_file_1.c
2 cl /c my_static_lib_file_2.c
3 lib /out:my_static_lib.lib my_static_lib_file_1.obj my_static_lib_file_2.obj
 
 
 
Die entstandene .lib Datei ist unsere statische Bibliothek und diese kann 
zum Linken des ausführbaren Programms einfach der Liste der zu linkenden 
Dateien beim Aufruf von link.exe hinzugefügt werden:
  1 cl /c myprog_file.c
2 link /out:myprog.exe myprog_file.obj my_static_lib.lib
 
 
 
 C Runtime statisch linken
In der Standard-Konfiguration, versucht der MSVC stets die C/C++-Runtime 
dynamisch zu linken. Das macht es erforderlich, dass man immer die C/C++
Runtime Libraries installieren muss, damit das eigene Programm läuft.

Mit der Kommandozeilen-Option /MT (bzw. /MTd im Debug Modus) wandert 
der C-Runtime Code “statisch” in die EXE und somit ist keine 
Runtime-Lib-Installation mehr notwendig.
  1 cl /c /MT my_static_lib_file_1.c
2 cl /c /MT myprog_file.c
 
 
 
 Linux mit GCC
Beim GCC werden statische Bibliotheken mit dem Tool-Aufruf ar -r aus 
kompilierten Objektdateien (.o) erzeugt und haben am Ende die 
Dateierweiterung .a.
Der erste Dateiname nach den Kommando-Optionen ist die Zieldatei der
statischen Bibliothek, danach folgen alle Objektdateien, die eingebunden
werden sollen.
 Statische Bibliothek erstellen und in ein Executable linken
Die GCC und Unix Konvention legt für alle Bibliotheken das Präfix 
lib fest. Aus dem Projekt namens my_static_lib wird dann eine 
libmy_static_lib.a Datei.
Dieser Konvention sollte man nach Möglichkeit folgen, damit alle weiteren
Tools die Bibliothek problemlos finden können.
  1 gcc -c my_static_lib_file_1.c
2 gcc -c my_static_lib_file_2.c
3 ar -r libmy_static_lib.a my_static_lib_file_1.o my_static_lib_file_2.o
 
 
 
Für C++ Dateien wird gcc einfach durch g++ ersetzt, 
der ar Aufruf bleibt hingegen gleich.
Zum Erstellen eines ausführbaren Programms, wird über die Linker Option
-l der Namen des Bibliothekenprojektes angegeben.
Der Parameter -lmy_static_lib bewirkt, dass libmy_static_lib.a statisch 
gelinkt wird. Falls es auch eine libmy_static_lib.so geben sollte,
kann man den Dateinamen auch vollständig angeben, z.B.: -llibmy_static_lib.a.
  1 gcc -c myprog_file.c
2 gcc myprog_file.o -lmy_static_lib -o myprog_file
 
 
 
 C Runtime statisch linken
Auch unter Linux wird die C-Runtime grundsätzlich dynamisch eingebunden.
Linkt man sie jedoch statisch, hat man bessere Chancen, dass ein Programm,
das in Distribution A erstellt wurde auch in Distribution B mit einer
anderen Standard-C-Runtime funktioniert.
Ehrlicherweise muss man aber auch sagen, dass das nicht immer empfohlen ist
und zwischen unterschiedlichen Distributionen auch Probleme mit statischen
C-Runtimes auftreten können (z.B. mit C++ std Exceptions).
Die Optionen -static-libgcc und -static-libstdc++ schalten die statische
Einbindung der C-Runtime und der C++-Runtime ein.
  1 gcc myprog_file.o -static-libgcc -lmy_static_lib -o myprog_file
2 g++ myprog_file.o -static-libgcc -static-libstdc++ -lmy_static_lib -o myprog_file
 
 
 
 Statisches Linken in CMake
In CMake gibt es zwei Möglichkeiten, 
wie man festlegt, dass eine Bibliothek statisch oder dynamisch (shared) 
werden kann.
  - Individuell in 
CMakeLists.txt in add_library
Mit dem Token STATIC nach dem Bibliotheksnamen wird sichergestellt,
dass der Code als statische Bibliothek gebaut wird:
    
  1 add_library(my_static_lib STATIC
2   my_static_lib_file_1.c
3   my_static_lib_file_2.c
4 )
 
 
     
   
  - Global über die CMake-Variable 
BUILD_SHARED_LIBS
Wenn man in add_library weder STATIC noch SHARED
explizit angibt, entscheidet die globale Variable BUILD_SHARED_LIBS,
wie die Bibliothek gebaut werden soll.
Ist sie nicht gesetzt oder OFF oder FALSE, entsteht eine
statische Bibliothek, andernfalls kommt eine dynamische (shared) Library
am Ende heraus.
    
  1 add_library(my_static_lib
2   my_static_lib_file_1.c
3   my_static_lib_file_2.c
4 )
 
 
     
    
  1 cmake -DBUILD_SHARED_LIBS=OFF .
 
 
     
   
 Ausführbares Programm in CMake gegen statische Libs linken lassen
Ausführbaren Programmen wird in CMake per 
target_link_libraries
mitgeteilt, welche Bibliotheken zu linken sind:
   1 add_executable(myprog
 2   myprog_file.c
 3 )
 4
 5 target_link_libraries(myprog
 6   my_static_lib
 7   other_lib
 8   ...
 9 )
 
 
 
Um ein ausführbares Programm zu bauen, sucht CMake die Bibliotheken 
automatisch. Es kann jedoch hilfreich sein mit 
set_target_properties
zu deklarieren, ob nach statischen oder dynamischen Libs zuerst gesucht wird:
  1 set_target_properties(myprog PROPERTIES 
2   LINK_SEARCH_START_STATIC 1
3 )
4 set_target_properties(myprog PROPERTIES 
5   LINK_SEARCH_END_STATIC 1
6 )
 
 
 
 C-Runtime in CMake statisch linken lassen
Auch in CMake kann es Sinn machen, die C-Runtime statisch einbinden zu lassen.
Dafür nutzen wir je nach Compiler-Typ die notwendigen Compiler-Switches.
   1 if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
 2   # MSVC
 3   set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT")
 4   set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd")
 5   set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
 6   set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
 7 elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
 8   # GCC 
 9   set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
10   set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")
11 endif()
 
 
 
 Statisches Linken in CONAN
In CONAN Umgebungen wird zwischen mit der “Option” 
shared festgelegt, wie eine Bibliothek gebaut oder gelinkt werden soll.
Da die benutzte Options-Variante den Hash eines CONAN Pakets ändert,
muss sowohl beim Bauen der Lib die korrekt Option gesetzt sein, als auch
bei der Einbindung als requirements.
 Statische Bibliothek mit CONAN erstellen
In conanfile.py kann der default-Wert der shared Option auf False
gesetzt werden um statische Libs per default zu erzeugen. Der 
conan build Schritt muss dann diese Information an das darunterliegende 
Build-System weiterreichen und auch die Paketierung wird am Ende je nach
Bibliothekstyp etwas anders aussehen:
   1 import os
 2 from conans import tools, ConanFile
 3
 4 class my_shared_lib(ConanFile):
 5   name = "my_shared_lib"
 6   version = "1.0"
 7   options = { "shared": [True, False] }
 8   default_options = { "shared": False }
 9
10   def build(self):
11     cmake = CMake(self)
12     if self.options.shared:
13       # dynamic
14       cmake.definitions["BUILD_SHARED_LIBS"] = "ON"
15     else:
16       # static
17       cmake.definitions["BUILD_SHARED_LIBS"] = "OFF"
18     cmake.build()
19  
20   def package(self):
21     if self.options.shared:
22       # dynamic
23       self.copy(pattern="*.dll", dst="bin", keep_path=False)
24       self.copy(pattern="*.lib", dst="lib", keep_path=False)
25       self.copy(pattern="*.dylib", dst="lib", keep_path=False)
26       self.copy(pattern="*.so*", dst="lib", keep_path=False)
27     else:
28       # static
29       self.copy(pattern="*.lib", dst="lib", keep_path=False)
30       self.copy(pattern="*.a", dst="lib", keep_path=False)
 
 
 
CONAN Optionen wie shared lassen sich dann an der Kommandozeile auch 
explizit setzen, wenn man von den default_options abweichen will:
  1 conan create path/to/conanfile.py -o my_shared_lib:shared=False
 
 
 
Alternativ kann man im aktuellen konfigurierten CONAN Profil auch eine 
Option setzen, die benutzt wird, falls sie durch keine andere Methode explizit
gesetzt wird:
  1 #~/.conan/profiles/default
2 ...
3 [options]
4 *:shared=False
 
 
 
 Statische Bibliothek mit CONAN in EXE linken
Bei den requirements des konsumierenden Projektes muss die gleiche
shared Option angefordert werden, mit der die Bibliothek gebaut wurde.
Andernfalls sucht CONAN nach dem falschen Paket-Hash in seinen Caches.
   1 import os
 2 from conans import tools, ConanFile
 3
 4 class myprog(ConanFile):
 5   name = "myprog"
 6   version = "1.0"
 7
 8   def requirements(self):
 9     self.requires("my_shared_lib/1.0@user/channel")
10     self.options["my_shared_lib"].shared = False
 
 
 
 Bekannte Problemfälle
Obwohl ich ein großer Fan von statischen Bibliotheken bin, habe ich auch 
einige Situationen kennenlernen dürfen, in denen es zu Problemen kommen kann.
Grundsätzlich sollte man sich an folgende Regel halten:
  Entweder, man nutzt nur statische Bibliotheken, 
oder man nutzt nur dynamische Bibliotheken.
Denn es sind in erster Linie die Mischformen, bei denen es zu unerwarteten
Problemen kommt.
Meiner Erfahrung nach, führt Microsoft’s MSVC ein paar Schritte aus, die
Probleme umgehen sollen. Im GCC finden diese vermutlich nicht statt und daher
kann ich über folgende Probleme berichten:
  - 
    
GCC Symbol-Zusammenführung
Wenn ein globales Symbol in mehreren Modulen vorkommt, legt der GCC die
Adressen in ein Symbol zusammen. Codes wie jene von Konstruktoren und
Destruktoren werden aber nicht zusammengelegt, sondern mehrfach ausgeführt.
Daraus ergibt sich das Problem:
    
      - Statische Lib definiert eine globale Variable
 
      - Dynamische Lib 
A nutzt statische Lib und “erbt” globale Variable 
      - Dynamische Lib 
B nutzt statische Lib und “erbt” ebenfalls eine globale 
Variable 
      - Programm lädt beide Libs.
 
      - Wir erhalten nur eine Adresse für die globale Variable aus beiden Modulen
 
      - Wir erhalten aber zwei Konstruktor und zwei Destruktor-Aufrufe, 
also je ein Aufruf pro Lib.
 
      - Bug: Es kommt zur Korruptionen beim zweiten Destruktor-Aufruf.
 
    
    Dieses Beispiel ist dokumentiert im Artikel:
double free wegen const std::string
   
  - 
    
GCC Code-Überschreibung von öffentlichen Funktionen
Wenn mehrere geladene dynamische Bibliotheken Funktionen mit gleichen Namen
exportieren, gewinnt eine der Funktionen und überschreibt die andere.
Dynamische Bibliotheken, die Funktionen einer gemeinsamen statischen 
Bibliothek exportieren, müssen alle mit dem gleichen Stand der statischen
Bibliothek kompiliert worden sein. Andernfalls rufen Objekte der dynamischen
Bibliothek Funktionen einer anderen Version der statischen Bibliothek auf,
als die, mit der sie selbst gebaut wurden.
    
      - Statische Lib stellte eine Funktion bereit.
 
      - Dynamische Lib 
A nutzt statische Lib und “erbt” und exportiert die Funktion 
      - Dynamische Lib 
B nutzt statische Lib und “erbt” und exportiert ebenfalls 
die Funktion. 
      - Statische Lib wird aktualisiert und erhält neue Features.
 
      - Nur dynamische Lib 
B wird neu kompiliert mit neuer Implementierung in 
statischer Lib. 
      - Programm lädt beide Libs 
A und B. 
      B verhält sich wie erwartet. 
      A sollte das alte Verhalten umsetzen, weil A nicht geändert wurde. 
      - Bug: 
A erhält aber Teile der Features der geänderten statischen Lib.
Was dann passiert kann unvorhersehbar sein. 
    
    Diese Problem ist in 
GCC Problem: Statische Shared-Libs
dokumentiert.
   
  - 
    
Link Time Optimization
Programme, die mit statischen Libs gebaut wurden haben in GCC Umgebungen 
einen wesentlich größeren Speicherbedarf, als z.B. beim MSVC, dessen 
Projekte per Standard die “Whole-Program-Optimization” nutzen.
Das GCC Äquivalent heißt “Link-Time-Optimization” (LTO)
und wird im Artikel GCC Binaries sind viel zu groß
beschrieben.
    Leider gibt es in manchen Umgebungen Probleme mit der LTO, weshalb sie
je nach Situation nicht fehlerfrei anwendbar ist.
    Ein Beispiel befindet sich in: GCC Link Time Optimization
   
 Weiterführende Links