Eine der wichtigsten Regeln und Best Practices beim Schreiben einer Bibliothek ist es, alle Symbole der Bibliothek in einen bibliotheksspezifischen Namensraum zu stellen. C++ macht dies einfach, aufgrund der namespace
Stichwort. In C besteht der übliche Ansatz darin, den Bezeichnern ein bibliotheksspezifisches Präfix voranzustellen.
Die Regeln des C-Standards setzen einige Einschränkungen für diese (für eine sichere Kompilierung): AC-Compiler dürfen nur die ersten 8 Zeichen eines Bezeichners betrachten, so foobar2k_eggs
und foobar2k_spam
können gültig als dieselben Bezeichner interpretiert werden – jedoch erlaubt jeder moderne Compiler beliebig lange Bezeichner, sodass wir uns in unserer Zeit (dem 21. Jahrhundert) nicht darum kümmern müssen.
Aber was ist, wenn Sie mit einigen Bibliotheken konfrontiert sind, deren Symbolnamen / Bezeichner Sie nicht ändern können? Vielleicht hast du nur ein statisches Binary und die Header bekommen oder willst oder darfst es nicht selbst anpassen und neu kompilieren.
Zumindest im Fall von statisch Bibliotheken können Sie ganz bequem umgehen.
Betrachten Sie diese Header von Bibliotheken foo und Bar. Für dieses Tutorial gebe ich Ihnen auch die Quelldateien
Beispiele/ex01/foo.h
int spam(void);
double eggs(void);
example/ex01/foo.c (dies kann undurchsichtig/nicht verfügbar sein)
int the_spams;
double the_eggs;
int spam()
{
return the_spams++;
}
double eggs()
{
return the_eggs--;
}
Beispiel/ex01/bar.h
int spam(int new_spams);
double eggs(double new_eggs);
example/ex01/bar.c (dies kann undurchsichtig/nicht verfügbar sein)
int the_spams;
double the_eggs;
int spam(int new_spams)
{
int old_spams = the_spams;
the_spams = new_spams;
return old_spams;
}
double eggs(double new_eggs)
{
double old_eggs = the_eggs;
the_eggs = new_eggs;
return old_eggs;
}
Wir wollen diese in einem Programm foobar verwenden
example/ex01/foobar.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
spam(new_bar_spam), new_bar_spam,
eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
Ein Problem wird sofort deutlich: C kennt kein Überladen. Wir haben also zwei mal zwei Funktionen mit identischem Namen, aber unterschiedlicher Signatur. Wir brauchen also eine Möglichkeit, diese zu unterscheiden. Mal sehen, was ein Compiler dazu zu sagen hat:
example/ex01/ $ make
cc -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1
Okay, das war keine Überraschung, es sagte uns nur, was wir bereits wussten oder zumindest vermuteten.
Können wir diese Identifer-Kollision also irgendwie lösen, ohne den Quellcode oder die Header der ursprünglichen Bibliotheken zu ändern? Tatsächlich können wir.
Lassen Sie uns zuerst die Kompilierzeitprobleme lösen. Dazu umgeben wir die Header-Includes mit einer Reihe von Präprozessoren #define
Direktiven, die allen von der Bibliothek exportierten Symbolen ein Präfix voranstellen. Später machen wir das mit einem netten, gemütlichen Wrapper-Header, aber nur um zu demonstrieren, was vor sich geht, haben wir es wörtlich in der gemacht foobar.c Quelldatei:
example/ex02/foobar.c
#include <stdio.h>
#define spam foo_spam
#define eggs foo_eggs
# include "foo.h"
#undef spam
#undef eggs
#define spam bar_spam
#define eggs bar_eggs
# include "bar.h"
#undef spam
#undef eggs
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
bar_spam(new_bar_spam), new_bar_spam,
bar_eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
Wenn wir das jetzt kompilieren…
example/ex02/ $ make
cc -c -o foobar.o foobar.c
cc foobar.o foo.o bar.o -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1
… es sieht zunächst so aus, als ob es schlimmer geworden wäre. Aber schaut genau hin: Eigentlich lief die Compilation-Phase ganz gut. Es ist nur der Linker, der sich jetzt darüber beschwert, dass Symbole kollidieren, und uns die Stelle (Quelldatei und Zeile) mitteilt, an der dies passiert. Und wie wir sehen können, haben diese Symbole kein Präfix.
Werfen wir einen Blick auf die Symboltabellen mit dem nm Dienstprogramm:
example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
Jetzt stehen wir also vor der Herausforderung, diesen Symbolen in einer undurchsichtigen Binärdatei ein Präfix voranzustellen. Ja, ich weiß, im Zuge dieses Beispiels haben wir die Quellen und könnten dies dort ändern. Aber nehmen Sie jetzt einfach an, dass Sie nur diese haben .Ö Dateien oder eine .a (was eigentlich nur ein Haufen ist .Ö).
Objektkopie zur Rettung
Ein Tool ist für uns besonders interessant: Objektkopie
objcopy arbeitet mit temporären Dateien, sodass wir es so verwenden können, als ob es vor Ort ausgeführt würde. Es gibt eine Option/Operation namens –Präfix-Symbole und Sie haben 3 Vermutungen, was es tut.
Also werfen wir diesen Kerl auf unsere sturen Bibliotheken:
example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o
nm zeigt uns, dass dies zu funktionieren schien:
example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams
example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams
Versuchen wir, das Ganze zu verlinken:
example/ex03/ $ make
cc foobar.o foo.o bar.o -o foobar
Und tatsächlich, es hat funktioniert:
example/ex03/ $ ./foobar
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000
Nun überlasse ich es dem Leser als Übung, ein Tool/Skript zu implementieren, das die Symbole einer Bibliothek automatisch extrahiert nmschreibt eine Wrapper-Header-Datei der Struktur
/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */
und wendet das Symbolpräfix mithilfe von auf die Objektdateien der statischen Bibliothek an Objektkopie.
Was ist mit gemeinsam genutzten Bibliotheken?
Im Prinzip könnte man dasselbe mit Shared Libraries machen. Shared Libraries werden jedoch, wie der Name schon sagt, von mehreren Programmen gemeinsam genutzt, daher ist es keine so gute Idee, auf diese Weise mit einer Shared Library herumzuspielen.
Sie werden nicht darum herumkommen, eine Trampolinfolie zu schreiben. Noch schlimmer ist, dass Sie auf Objektdateiebene nicht mit der gemeinsam genutzten Bibliothek verknüpfen können, sondern gezwungen sind, dynamisch zu laden. Aber das verdient einen eigenen Artikel.
Bleiben Sie dran und viel Spaß beim Programmieren.
Die Regeln des C-Standards setzen diesen einige Einschränkungen (für eine sichere Kompilierung): Der AC-Compiler kann nur die ersten 8 Zeichen eines Bezeichners betrachten, sodass foobar2k_eggs und foobar2k_spam gültig als dieselben Bezeichner interpretiert werden können – jedoch lässt jeder moderne Compiler beliebige zu lange Identifikatoren, also sollten wir uns in unserer Zeit (im 21. Jahrhundert) nicht darum kümmern müssen.
Dies ist nicht nur eine Erweiterung moderner Compiler; der aktuelle C-Standard auch erfordert den Compiler, um einigermaßen lange externe Namen zu unterstützen. Ich habe die genaue Länge vergessen, aber es sind jetzt ungefähr 31 Zeichen, wenn ich mich recht erinnere.
Aber was ist, wenn Sie mit einigen Bibliotheken konfrontiert sind, deren Symbolnamen / Bezeichner Sie nicht ändern können? Vielleicht hast du nur ein statisches Binary und die Header bekommen oder willst oder darfst es nicht selbst anpassen und neu kompilieren.
Dann steckst du fest. Beschweren Sie sich beim Autor der Bibliothek. Ich bin einmal auf einen solchen Fehler gestoßen, bei dem Benutzer meiner Anwendung sie aufgrund von Debian nicht auf Debian erstellen konnten libSDL
verlinken libsoundfile
die (zumindest damals) den globalen Namensraum fürchterlich mit Variablen wie verschmutzte dsp
(Das ist kein Scherz!). Ich habe mich bei Debian beschwert, und sie haben ihre Pakete repariert und den Fix nach oben geschickt, wo ich annehme, dass er angewendet wurde, da ich nie wieder von dem Problem gehört habe.
Ich denke wirklich, dass dies der beste Ansatz ist, weil es das Problem löst für jeden. Jeder lokale Hack, den Sie durchführen, hinterlässt das Problem in der Bibliothek, damit der nächste unglückliche Benutzer erneut darauf stoßen und mit ihm kämpfen kann.
Wenn Sie wirklich eine schnelle Lösung benötigen und eine Quelle haben, können Sie eine Reihe von hinzufügen -Dfoo=crappylib_foo -Dbar=crappylib_bar
usw. in das Makefile, um es zu beheben. Wenn nicht, verwenden Sie die objcopy
Lösung, die Sie gefunden haben.
Wenn Sie GCC verwenden, ist der Linker-Schalter –allow-multiple-definition ein praktisches Debugging-Tool. Dies zwingt den Linker dazu, die erste Definition zu verwenden (und nicht darüber zu jammern). Mehr dazu hier.
Dies hat mir während der Entwicklung geholfen, wenn ich die Quelle einer vom Anbieter bereitgestellten Bibliothek zur Verfügung habe und aus irgendeinem Grund eine Bibliotheksfunktion nachverfolgen muss. Der Schalter ermöglicht es Ihnen, eine lokale Kopie einer Quelldatei zu kompilieren und zu verknüpfen und dennoch mit der unveränderten statischen Anbieterbibliothek zu verknüpfen. Vergessen Sie nicht, den Schalter wieder aus den Make-Symbolen herauszuziehen, sobald die Entdeckungsreise abgeschlossen ist. Der Versandfreigabecode mit absichtlichen Namensraumkollisionen ist anfällig für Fallstricke, einschließlich unbeabsichtigt Namespace-Kollisionen.
Siehe auch: stackoverflow.com/questions/6538501/…
– ninjalj
4. August 2011 um 15:04 Uhr