Wann werden dynamische oder statische Bibliotheken verwendet?


437

Beim Erstellen einer Klassenbibliothek in C ++ können Sie zwischen dynamischen ( .dll, .so) und statischen ( .lib, .a) Bibliotheken wählen . Was ist der Unterschied zwischen ihnen und wann ist es angebracht, welche zu verwenden?


2
Es sollte beachtet werden, dass es auch etwas gibt, das als "Import Library" bezeichnet wird. Check stackoverflow.com/questions/3573475/…
Wakan Tanka

Antworten:


299

Statische Bibliotheken vergrößern den Code in Ihrer Binärdatei. Sie werden immer geladen und jede Version des Codes, mit dem Sie kompiliert haben, ist die Version des Codes, der ausgeführt wird.

Dynamische Bibliotheken werden separat gespeichert und versioniert. Es ist möglich, dass eine Version der dynamischen Bibliothek geladen wird, die nicht die Originalversion ist, die mit Ihrem Code geliefert wurde, wenn das Update als binär kompatibel mit der Originalversion angesehen wird.

Außerdem werden dynamische Bibliotheken nicht unbedingt geladen - sie werden normalerweise beim ersten Aufruf geladen - und können von Komponenten gemeinsam genutzt werden, die dieselbe Bibliothek verwenden (mehrere Daten werden geladen, ein Code wird geladen).

Dynamische Bibliotheken wurden die meiste Zeit als der bessere Ansatz angesehen, aber ursprünglich hatten sie einen großen Fehler (Google DLL Hell), der durch neuere Windows-Betriebssysteme (insbesondere Windows XP) so gut wie beseitigt wurde.


71
Unter Windows / Mac (kein Paketmanager) gibt es keinen guten Grund, dynamische Bibliotheken über statische zu verwenden. Da Windows-DLLs nicht verschiebbar sind, funktioniert die Codefreigabe häufig nicht (und normalerweise wird jede App ausgeliefert und verwendet sowieso ihre eigenen Versionen der Bibliothek). Der einzige wirkliche Vorteil ist, dass es einfacher ist, die Bibliothek zu aktualisieren.
Zifre

5
Auf dem Mac verwende ich viele dynamische Bibliotheken. In Mac OS X ist beispielsweise sqlite3 eingebettet. Ich habe ein Programm erstellt, das eine SQLite3-Datenbankfunktion zum Speichern von Leistung hat. Da dynamische Verknüpfungen jedoch nur selten verwendet werden, spart dies Kompilierungszeit und erleichtert das Testen. Wenn ich jedoch eine Release-Version erstellen würde, würde ich bei Kompatibilitätsproblemen immer eine statische Bibliothek verwenden
ReachConnection,

6
@Zifre: relocatable = kann an einer anderen virtuellen Adresse geladen werden. DLL unterstützt dies sicherlich.
dma_k

20
@dma_k: Windows-DLLs können an verschiedenen Adressen geladen werden, jedoch nur, weil der Linker den gesamten Code kopiert und die Adressnummern ändert. Bei gemeinsam genutzten Objekten sind alle Adressreferenzen relativ, sodass mehrere Prozesse denselben Speicher für das gemeinsam genutzte Objekt gemeinsam nutzen können. Mit anderen Worten, unter Windows ist eine von 3 Programmen verwendete 1-MB-DLL = 3 MB. Unter Linux ist ein von 3 Programmen verwendeter MB SO = 1 MB.
Zifre

7
Sowohl Windows als auch Linux haben das Konzept der zeitgesteuerten Verlagerung gemeinsam genutzter Bibliotheken eli.thegreenplace.net/2011/08/25/…. Das Größte, was position Independent Code ermöglichte, war für Linux nichts Besonderes, sondern es wurde eine RIP-relative Adressierung hinzugefügt mit dem x64-Befehlssatz; Sowohl Windows als auch Linux können die relative RIP-Adressierung verwenden, um die Anzahl der Korrekturen beim Verschieben von Bibliotheken zu verringern.
Clemahieu

194

Andere haben angemessen erklärt, was eine statische Bibliothek ist, aber ich möchte auf einige der Vorbehalte bei der Verwendung statischer Bibliotheken hinweisen, zumindest unter Windows:

  • Singletons: Wenn etwas global / statisch und einzigartig sein muss, sollten Sie es sehr vorsichtig in eine statische Bibliothek stellen. Wenn mehrere DLLs mit dieser statischen Bibliothek verknüpft sind, erhalten sie jeweils eine eigene Kopie des Singletons. Wenn Ihre Anwendung jedoch eine einzelne EXE-Datei ohne benutzerdefinierte DLLs ist, ist dies möglicherweise kein Problem.

  • Entfernen von nicht referenziertem Code: Wenn Sie eine Verknüpfung zu einer statischen Bibliothek herstellen, werden nur die Teile der statischen Bibliothek, auf die Ihre DLL / EXE verweist, mit Ihrer DLL / EXE verknüpft.

    Wenn beispielsweise mylib.libenthält a.objund b.objund Ihre DLL / EXE nur Verweise Funktionen oder Variablen aus a.obj, die Gesamtheit b.objwird durch den Linker verworfen bekommen. Wenn sie b.objglobale / statische Objekte enthalten, werden ihre Konstruktoren und Destruktoren nicht ausgeführt. Wenn diese Konstruktoren / Destruktoren Nebenwirkungen haben, können Sie von ihrer Abwesenheit enttäuscht sein.

    Wenn die statische Bibliothek spezielle Einstiegspunkte enthält, müssen Sie möglicherweise darauf achten, dass diese tatsächlich enthalten sind. Ein Beispiel hierfür in der eingebetteten Programmierung (okay, nicht Windows) wäre ein Interrupt-Handler, der als an einer bestimmten Adresse markiert ist. Sie müssen den Interrupt-Handler auch als Einstiegspunkt markieren, um sicherzustellen, dass er nicht verworfen wird.

    Eine weitere Folge davon ist, dass eine statische Bibliothek möglicherweise Objektdateien enthält, die aufgrund nicht aufgelöster Referenzen vollständig unbrauchbar sind. Sie verursacht jedoch erst dann einen Linkerfehler, wenn Sie auf eine Funktion oder Variable aus diesen Objektdateien verweisen. Dies kann lange nach dem Schreiben der Bibliothek geschehen.

  • Debug-Symbole: Möglicherweise möchten Sie für jede statische Bibliothek einen separaten PDB, oder Sie möchten, dass die Debug-Symbole in den Objektdateien platziert werden, damit sie in den PDB für die DLL / EXE übernommen werden. In der Visual C ++ - Dokumentation werden die erforderlichen Optionen erläutert .

  • RTTI:type_info Wenn Sie eine einzelne statische Bibliothek mit mehreren DLLs verknüpfen, erhalten Sie möglicherweise mehrere Objekte für dieselbe Klasse. Wenn Ihr Programm davon ausgeht, dass type_infoes sich um "Singleton" -Daten handelt und &typeid()oder verwendet type_info::before(), erhalten Sie möglicherweise unerwünschte und überraschende Ergebnisse.


23
Was Singletons betrifft, vergessen Sie nicht, dass eine DLL möglicherweise mehrmals geladen wird (dieselbe Version oder mehrere Versionen) und es immer noch keine Singleton-Garantie gibt.
Orion Adrian

Zusätzlicher Punkt zum Entfernen von nicht referenziertem Code: Aufrufe von DLLs erfordern auch einen tatsächlichen Aufruf, um das Laden der referenzierten DLL zu erzwingen. Wenn Sie es als Referenz hinzufügen, aber keinen Aufruf einschließen, der darauf verweist, erhalten Sie immer noch das gleiche Ergebnis wie bei einer statischen Bibliothek, die nichts aufruft. Der einzige Unterschied ist, was tatsächlich versendet wird. In beiden Fällen werden die statischen Konstruktoren und Destruktoren nicht ausgelöst.
Orion Adrian

@ bk1e Das sollte nicht passieren. Die .a enthält immer alle Symbole, mit denen sie erstellt wurde. Wenn es statisch mit Ihrer Anwendung verknüpft ist, werden nur die verwendeten Symbole verknüpft.
Miles Rout

62

Eine lib ist eine Codeeinheit, die in Ihrer ausführbaren Anwendungsdatei gebündelt ist.

Eine DLL ist eine eigenständige Einheit von ausführbarem Code. Es wird dabei nur geladen, wenn ein Aufruf dieses Codes erfolgt. Eine DLL kann von mehreren Anwendungen verwendet und in mehreren Prozessen geladen werden, wobei immer noch nur eine Kopie des Codes auf der Festplatte vorhanden ist.

DLL-Profis : können verwendet werden, um Code zwischen mehreren Produkten wiederzuverwenden / zu teilen; Laden bei Bedarf in den Prozessspeicher und kann bei Nichtgebrauch entladen werden; kann unabhängig vom Rest des Programms aktualisiert werden.

DLL-Nachteile : Auswirkungen auf die Leistung beim Laden der DLL und beim erneuten Basieren des Codes; Versionsprobleme ("dll hell")

Lib-Profis : Keine Auswirkungen auf die Leistung, da Code immer in den Prozess geladen und nicht neu basiert wird. Keine Versionsprobleme.

Lib cons : ausführbare Datei / Prozess "aufblähen" - Der gesamte Code befindet sich in Ihrer ausführbaren Datei und wird beim Prozessstart geladen. Keine Wiederverwendung / Weitergabe - jedes Produkt hat eine eigene Kopie des Codes.


Das Wiederherstellen kann auch zur Erstellungszeit mithilfe von rebase.exe oder durch Übergeben der Option / BASE an link.exe erfolgen. Ob dies effektiv ist, hängt davon ab, ob zur Laufzeit unerwartete Adressraumkonflikte auftreten.
bk1e

24

Neben den technischen Auswirkungen von statischen und dynamischen Bibliotheken (statische Dateien bündeln alles in einer großen binären oder dynamischen Bibliothek, die die gemeinsame Nutzung von Code zwischen mehreren verschiedenen ausführbaren Dateien ermöglicht) gibt es rechtliche Auswirkungen .

Wenn Sie beispielsweise LGPL-lizenzierten Code verwenden und statisch mit einer LGPL-Bibliothek verknüpfen (und somit eine große Binärdatei erstellen), wird Ihr Code automatisch zu Open Sourced- LGPL-Code ( kostenlos wie in Freiheit) . Wenn Sie eine Verknüpfung zu freigegebenen Objekten herstellen, müssen Sie nur die Verbesserungen / Fehlerbehebungen vornehmen, die Sie an der LGPL-Bibliothek selbst vorgenommen haben.

Dies wird zu einem weitaus wichtigeren Problem, wenn Sie beispielsweise entscheiden, wie Sie Ihre mobilen Anwendungen kompilieren möchten (bei Android haben Sie die Wahl zwischen statisch und dynamisch, bei iOS nicht - es ist immer statisch).


22

C ++ - Programme werden in zwei Phasen erstellt

  1. Kompilierung - erzeugt Objektcode (.obj)
  2. Verknüpfen - erzeugt ausführbaren Code (.exe oder .dll)

Die statische Bibliothek (.lib) ist nur ein Bündel von .obj-Dateien und daher kein vollständiges Programm. Es hat nicht die zweite (Verknüpfungs-) Phase des Erstellens eines Programms durchlaufen. DLLs hingegen sind wie Exes und daher vollständige Programme.

Wenn Sie eine statische Bibliothek erstellen, ist diese noch nicht verknüpft. Daher müssen Benutzer Ihrer statischen Bibliothek denselben Compiler verwenden, den Sie verwendet haben (wenn Sie g ++ verwendet haben, müssen sie g ++ verwenden).

Wenn Sie stattdessen eine DLL erstellt haben (und diese korrekt erstellt haben ), haben Sie ein vollständiges Programm erstellt, das alle Benutzer verwenden können, unabhängig davon, welchen Compiler sie verwenden. Es gibt jedoch verschiedene Einschränkungen beim Exportieren aus einer DLL, wenn Cross-Compiler-Kompatibilität gewünscht wird.


1
Das sind Neuigkeiten für mich. Welche Einschränkungen gibt es bei Cross-Compilern bei der Verwendung von DLLs? Den Programmierer ohne die gleiche Toolchain erstellen zu lassen, scheint ein großes Plus für DLLs zu sein
Dan

1
Diese Antwort ist informativ. Hinzufügen einer kleinen Einschränkung: consumers of your static library will have to use the same compiler that you usedWenn die statische Bibliothek eine C ++ - Bibliothek verwendet, z #include <iostream>.
Truthadjustr

Eine c ++ - DLL kann nur verwendet werden, wenn derselbe Compiler verwendet wird (da es kein Standard-c ++ abi gibt, werden Symbole auf unterschiedliche Weise entstellt). Sowohl die DLL als auch das Client-Modul müssen denselben Compiler und dieselben Build-Einstellungen verwenden
kcris

18

Erstellen einer statischen Bibliothek

$$:~/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]>

Erstellen einer dynamischen Bibliothek

$$:~/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]>

13

Eine statische Bibliothek wird in den Client kompiliert. Eine .lib wird zur Kompilierungszeit verwendet und der Inhalt der Bibliothek wird Teil der konsumierenden ausführbaren Datei.

Eine dynamische Bibliothek wird zur Laufzeit geladen und nicht in die ausführbare Client-Datei kompiliert. Dynamische Bibliotheken sind flexibler, da mehrere ausführbare Clientdateien eine DLL laden und ihre Funktionalität nutzen können. Dies reduziert auch die Gesamtgröße und Wartbarkeit Ihres Client-Codes auf ein Minimum.


13

Sie sollten sorgfältig über Änderungen im Laufe der Zeit, Versionierung, Stabilität, Kompatibilität usw. nachdenken.

Wenn es zwei Apps gibt, die den gemeinsam genutzten Code verwenden, möchten Sie diese Apps zwingen, sich gemeinsam zu ändern, falls sie miteinander kompatibel sein müssen? Dann benutze die DLL. Alle Exes verwenden denselben Code.

Oder möchten Sie sie voneinander isolieren, damit Sie eine ändern und sicher sein können, dass Sie die andere nicht gebrochen haben? Verwenden Sie dann die statische Bibliothek.

DLL Hölle ist, wenn Sie wahrscheinlich eine statische Bibliothek verwendet haben sollten, aber Sie haben stattdessen eine DLL verwendet, und nicht alle Exes sind damit kompatibel.


9

Eine statische Bibliothek muss mit der endgültigen ausführbaren Datei verknüpft sein. es wird Teil der ausführbaren Datei und folgt ihr, wohin sie auch geht. Bei jeder Ausführung der ausführbaren Datei wird eine dynamische Bibliothek geladen, die als DLL-Datei von der ausführbaren Datei getrennt bleibt.

Sie würden eine DLL verwenden, wenn Sie die von der Bibliothek bereitgestellten Funktionen ändern möchten, ohne die ausführbare Datei erneut verknüpfen zu müssen (ersetzen Sie einfach die DLL-Datei, ohne die ausführbare Datei ersetzen zu müssen).

Sie würden eine statische Bibliothek verwenden, wenn Sie keinen Grund haben, eine dynamische Bibliothek zu verwenden.


Sie können eine DLL auch verwenden, wenn mehrere andere Anwendungen dieselbe Funktionalität verwenden. Dies kann den Platzbedarf verringern.
Tim

Die Erweiterung Ihres ursprünglichen Konzepts "Plug-in" -Architektur, bei der Sie später hinzugefügte / unbekannte Funktionen zulassen möchten, ohne sie neu erstellen oder neu veröffentlichen zu müssen, kann nur mit dynamischen Bibliotheken durchgeführt werden.
Tim

8

Ulrich Dreppers Artikel über " Wie man gemeinsam genutzte Bibliotheken schreibt " ist auch eine gute Ressource, in der detailliert beschrieben wird, wie gemeinsam genutzte Bibliotheken am besten genutzt werden können oder was er als "Dynamic Shared Objects" (DSOs) bezeichnet. Es konzentriert sich mehr auf gemeinsam genutzte Bibliotheken im ELF- Binärformat, aber einige Diskussionen eignen sich auch für Windows-DLLs.


5

Lesen Sie diesen Artikel von Sun, um eine hervorragende Diskussion zu diesem Thema zu erhalten .

Es geht um alle Vorteile, einschließlich der Möglichkeit, zwischengeschaltete Bibliotheken einzufügen. Weitere Einzelheiten zum Einfügen finden Sie in diesem Artikel hier .


4

Der Kompromiss, den Sie (in einem großen Projekt) eingehen, liegt in der anfänglichen Ladezeit. Die Bibliotheken werden zu dem einen oder anderen Zeitpunkt verknüpft. Die Entscheidung, die getroffen werden muss, ist, dass die Verknüpfung lange genug dauert, bis der Compiler sie benötigt um die Kugel zu beißen und es vorne zu tun, oder kann der dynamische Linker es beim Laden tun.


3

Wenn Ihre Bibliothek von mehreren ausführbaren Dateien gemeinsam genutzt werden soll, ist es häufig sinnvoll, sie dynamisch zu gestalten, um die Größe der ausführbaren Dateien zu verringern. Ansonsten machen Sie es auf jeden Fall statisch.

Die Verwendung einer DLL hat mehrere Nachteile. Es gibt zusätzlichen Aufwand für das Laden und Entladen. Es gibt auch eine zusätzliche Abhängigkeit. Wenn Sie die DLL so ändern, dass sie nicht mehr mit Ihren ausführbaren Dateien kompatibel ist, funktionieren diese nicht mehr. Wenn Sie dagegen eine statische Bibliothek ändern, sind Ihre kompilierten ausführbaren Dateien, die die alte Version verwenden, nicht betroffen.


3

Wenn die Bibliothek statisch ist, wird der Code zum Zeitpunkt der Verknüpfung mit Ihrer ausführbaren Datei verknüpft. Dies macht Ihre ausführbare Datei größer (als wenn Sie den dynamischen Weg gegangen wären).

Wenn die Bibliothek dynamisch ist, werden zur Verknüpfungszeit Verweise auf die erforderlichen Methoden in Ihre ausführbare Datei integriert. Dies bedeutet, dass Sie Ihre ausführbare Datei und die dynamische Bibliothek versenden müssen. Sie sollten auch überlegen, ob der gemeinsame Zugriff auf den Code in der Bibliothek eine sichere, bevorzugte Ladeadresse ist.

Wenn Sie mit der statischen Bibliothek leben können, gehen Sie mit der statischen Bibliothek.


3

Wir verwenden viele DLLs (> 100) in unserem Projekt. Diese DLLs sind voneinander abhängig und daher haben wir uns für die Einrichtung der dynamischen Verknüpfung entschieden. Es hat jedoch die folgenden Nachteile:

  • langsamer Start (> 10 Sekunden)
  • DLLs mussten versioniert werden, da Windows Module nach Eindeutigkeit von Namen lädt. Eigene geschriebene Komponenten würden sonst die falsche Version der DLL erhalten (dh die bereits geladene anstelle ihrer eigenen verteilten Menge).
  • Der Optimierer kann nur innerhalb von DLL-Grenzen optimieren. Das Optimierungsprogramm versucht beispielsweise, häufig verwendete Daten und Code nebeneinander zu platzieren. Dies funktioniert jedoch nicht über DLL-Grenzen hinweg

Vielleicht war ein besseres Setup, alles zu einer statischen Bibliothek zu machen (und deshalb haben Sie nur eine ausführbare Datei). Dies funktioniert nur, wenn keine Codeduplizierung stattfindet. Ein Test scheint diese Annahme zu stützen, aber ich konnte kein offizielles MSDN-Zitat finden. So machen Sie zum Beispiel 1 exe mit:

  • exe verwendet shared_lib1, shared_lib2
  • shared_lib1 benutze shared_lib2
  • shared_lib2

Der Code und die Variablen von shared_lib2 sollten in der endgültigen zusammengeführten ausführbaren Datei nur einmal vorhanden sein. Kann jemand diese Frage unterstützen?


Wollen Sie nicht einige Pre-Compiler-Direktiven verwenden, um Codeduplikationen zu vermeiden?
Paceman

Die Vorkompilierung von Afaiac funktioniert nur auf Basis eines Moduls (exe / dll / lib). Das Vorkompilieren soll in erster Linie die Kompilierung beschleunigen, verhindert jedoch auch mehrere Einschlüsse innerhalb einer Kompilierungseinheit. Einschlussschutz ist jedoch der bessere Weg, um diesen Effekt zu erzielen.
Gast128

2

Statische Bibliotheken sind Archive, die den Objektcode für die Bibliothek enthalten, wenn sie mit einer Anwendung verknüpft sind, wird dieser Code in die ausführbare Datei kompiliert. Freigegebene Bibliotheken unterscheiden sich darin, dass sie nicht in die ausführbare Datei kompiliert werden. Stattdessen durchsucht der dynamische Linker einige Verzeichnisse nach den benötigten Bibliotheken und lädt diese dann in den Speicher. Mehr als eine ausführbare Datei kann dieselbe gemeinsam genutzte Bibliothek gleichzeitig verwenden, wodurch die Speichernutzung und die Größe der ausführbaren Datei verringert werden. Es gibt dann jedoch mehr Dateien, die mit der ausführbaren Datei verteilt werden müssen. Sie müssen sicherstellen, dass die Bibliothek auf dem Verwendungssystem installiert ist, auf dem der Linker sie finden kann. Durch statische Verknüpfung wird dieses Problem behoben, es wird jedoch eine größere ausführbare Datei erstellt.


2

Wenn Sie nur an eingebetteten Projekten oder speziellen Plattformen arbeiten, sind statische Bibliotheken der einzige Weg, und auch das Kompilieren in Ihre Anwendung ist oftmals weniger mühsam. Auch Projekte und Makefiles, die alles enthalten, machen das Leben glücklicher.


2

Ich würde eine allgemeine Faustregel geben: Wenn Sie eine große Codebasis haben, die alle auf Bibliotheken niedrigerer Ebenen (z. B. einem Utils- oder Gui-Framework) basiert, die Sie in besser verwaltbare Bibliotheken aufteilen möchten, werden diese zu statischen Bibliotheken. Dynamische Bibliotheken kaufen Ihnen eigentlich nichts und es gibt weniger Überraschungen - es wird zum Beispiel nur eine Instanz von Singletons geben.

Wenn Sie eine Bibliothek haben, die vollständig vom Rest der Codebasis getrennt ist (z. B. eine Bibliothek eines Drittanbieters), sollten Sie sie zu einer DLL machen. Wenn die Bibliothek LGPL ist, müssen Sie aufgrund der Lizenzbedingungen möglicherweise trotzdem eine DLL verwenden.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.