Jedną z najważniejszych zasad i najlepszych praktyk podczas pisania biblioteki jest umieszczenie wszystkich symboli biblioteki w określonej przestrzeni nazw biblioteki. C ++ ułatwia to dzięki namespace
słowu kluczowemu. W C typowym podejściem jest poprzedzanie identyfikatorów pewnym przedrostkiem specyficznym dla biblioteki.
Zasady standardu C umieścić pewne ograniczenia dotyczące tych (na bezpiecznej kompilacji): AC kompilator może odczytywać tylko 8 pierwszych znaków identyfikatora, więc foobar2k_eggs
i foobar2k_spam
nie może być interpretowane jako tych samych identyfikatorów ważnie - jednak każdy nowoczesny kompilator pozwala na arbitralne długich identyfikatorów więc w naszych czasach (XXI wiek) nie powinniśmy się tym przejmować.
Ale co, jeśli masz do czynienia z bibliotekami, w których nie możesz zmienić nazw symboli / identyfikatorów? Być może masz tylko statyczny plik binarny i nagłówki lub nie chcesz lub nie możesz samodzielnie dostosowywać i rekompilować.
Odpowiedzi:
Przynajmniej w przypadku bibliotek statycznych można to dość wygodnie obejść.
Rozważ te nagłówki bibliotek foo i bar . Ze względu na ten samouczek podam również pliki źródłowe
przykłady / ex01 / foo.h
int spam(void); double eggs(void);
przykłady / ex01 / foo.c (to może być nieprzezroczyste / niedostępne)
int the_spams; double the_eggs; int spam() { return the_spams++; } double eggs() { return the_eggs--; }
przykład / ex01 / bar.h
int spam(int new_spams); double eggs(double new_eggs);
przykłady / ex01 / bar.c (może być nieprzezroczysty / niedostępny)
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; }
Chcemy ich użyć w programie foobar
przykład / 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; }
Jeden problem staje się natychmiast widoczny: C nie zna przeciążenia. Mamy więc dwa razy dwie funkcje o identycznej nazwie, ale różnej sygnaturze. Potrzebujemy więc jakiegoś sposobu, aby je rozróżnić. W każdym razie zobaczmy, co na ten temat ma do powiedzenia kompilator:
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
Okej, to nie była niespodzianka, po prostu powiedziało nam, co już wiedzieliśmy lub przynajmniej podejrzewaliśmy.
Czy możemy więc w jakiś sposób rozwiązać tę kolizję identyfikatorów bez modyfikowania kodu źródłowego lub nagłówków oryginalnych bibliotek? W rzeczywistości możemy.
Najpierw rozwiążmy problemy związane z czasem kompilacji. W tym celu otoczymy nagłówki zawierające kilka
#define
dyrektyw preprocesora, które poprzedzają wszystkie symbole eksportowane przez bibliotekę. Później robimy to z ładnym, przytulnym nagłówkiem wrapper, ale tylko po to, aby zademonstrować, co się dzieje, robiliśmy to dosłownie w pliku źródłowym foobar.c :przykład / 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; }
Teraz, jeśli skompilujemy to ...
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
... najpierw wygląda na to, że sytuacja się pogorszyła. Ale spójrz uważnie: Właściwie etap kompilacji poszedł dobrze. To tylko konsolidator narzeka teraz, że symbole kolidują i informuje nas o lokalizacji (plik źródłowy i wiersz), w którym to się dzieje. Jak widzimy, te symbole nie mają przedrostków.
Rzućmy okiem na tablice symboli za pomocą narzędzia nm :
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
So now we're challenged with the exercise to prefix those symbols in some opaque binary. Yes, I know in the course of this example we have the sources and could change this there. But for now, just assume you have only those .o files, or a .a (which actually is just a bunch of .o).
objcopy to the rescue
There is one tool particularily interesting for us: objcopy
objcopy works on temporary files, so we can use it as if it were operating in-place. There is one option/operation called --prefix-symbols and you have 3 guesses what it does.
So let's throw this fella onto our stubborn libraries:
nm shows us that this seemed to work:
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
Lets try linking this whole thing:
And indeed, it worked:
example/ex03/ $ ./foobar foo: spam = 0, eggs = 0.000000 bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000
Now I leave it as an exercise to the reader to implement a tool/script that automatically extracts the symbols of a library using nm, writes a wrapper header file of the structure
/* wrapper header wrapper_foo.h for foo.h */ #define spam foo_spam #define eggs foo_eggs /* ... */ #include <foo.h> #undef spam #undef eggs /* ... */
and applies the symbol prefix to the static library's object files using objcopy.
What about shared libraries?
In principle the same could be done with shared libraries. However shared libraries, the name tells it, are shared among multiple programs, so messing with a shared library in this way is not such a good idea.
You will not get around writing a trampoline wrapper. Even worse you cannot link against the shared library on the object file level, but are forced to do dynamic loading. But this deserves its very own article.
Stay tuned, and happy coding.
źródło
objcopy
.objcopy --prefix-symbols
... +1!This is not just an extension of modern compilers; the current C standard also requires the compiler to support reasonably long external names. I forget the exact length but it's something like 31 characters now if I remember right.
Then you're stuck. Complain to the author of the library. I once encountered such a bug where users of my application were unable to build it on Debian due to Debian's
libSDL
linkinglibsoundfile
, which (at least at the time) polluted the global namespace horribly with variables likedsp
(I kid you not!). I complained to Debian, and they fixed their packages and sent the fix upstream, where I assume it was applied, since I never heard of the problem again.I really think this is the best approach, because it solves the problem for everyone. Any local hack you do will leave the problem in the library for the next unfortunate user to encounter and fight with again.
If you really do need a quick fix, and you have source, you could add a bunch of
-Dfoo=crappylib_foo -Dbar=crappylib_bar
etc. to the makefile to fix it. If not, use theobjcopy
solution you found.źródło
If you're using GCC, the --allow-multiple-definition linker switch is a handy debugging tool. This hogties the linker into using the first definition (and not whining about it). More about it here.
This has helped me during development when I have the source to a vendor-supplied library available and need to trace into a library function for some reason or other. The switch allows you to compile and link in a local copy of a source file and still link to the unmodified static vendor library. Don't forget to yank the switch back out of the make symbols once the voyage of discovery is complete. Shipping release code with intentional name space collisions is prone to pitfalls including unintentional name space collisions.
źródło