Одно из самых важных правил и передовых практик при написании библиотеки - поместить все символы библиотеки в пространство имен, специфичное для библиотеки. C ++ упрощает это благодаря namespace
ключевому слову. В C обычный подход заключается в добавлении к идентификаторам префикса, специфичного для библиотеки.
Правила стандарта C поставить некоторые ограничения на тех (для безопасной компиляции): AC компилятор может смотреть на только первые 8 символах идентификатора, так foobar2k_eggs
и foobar2k_spam
может быть интерпретирован как те же идентификаторы действительных - однако каждый современный компилятор допускает произвольные длинные идентификаторы , поэтому в наше время (21 век) мы не должны об этом беспокоиться.
Но что, если вы сталкиваетесь с некоторыми библиотеками, в которых вы не можете изменить имена / идентификаторы символов? Возможно, у вас есть только статический двоичный файл и заголовки, или вы не хотите, или вам не разрешено настраивать и перекомпилировать самостоятельно.
Ответы:
По крайней мере, в случае со статическими библиотеками вы можете довольно удобно обойти это.
Рассмотрим заголовки библиотек foo и bar . Для этого урока я также дам вам исходные файлы
примеры / ex01 / foo.h
int spam(void); double eggs(void);
examples / ex01 / foo.c (может быть непрозрачным / недоступным)
int the_spams; double the_eggs; int spam() { return the_spams++; } double eggs() { return the_eggs--; }
пример / ex01 / bar.h
int spam(int new_spams); double eggs(double new_eggs);
examples / ex01 / bar.c (может быть непрозрачным / недоступным)
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; }
Мы хотим использовать их в программе foobar
пример / 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; }
Одна проблема становится очевидной сразу: C не знает перегрузки. Итак, у нас есть дважды две функции с одинаковым именем, но разной подписью. Итак, нам нужен способ их различать. В любом случае, давайте посмотрим, что компилятор скажет по этому поводу:
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
Ладно, это не было сюрпризом, он просто сказал нам то, что мы уже знали или, по крайней мере, подозревали.
Итак, можем ли мы каким-то образом разрешить конфликт идентификаторов без изменения исходного кода или заголовков исходных библиотек? На самом деле мы можем.
Во-первых, давайте решим проблемы времени компиляции. Для этого мы окружаем заголовок, содержащий кучу
#define
директив препроцессора, которые префикс всех символов, экспортируемых библиотекой. Позже мы сделаем это с помощью приятного уютного заголовка-оболочки, но просто для демонстрации происходящего мы дословно делали это в исходном файле foobar.c :пример / 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; }
Теперь, если мы скомпилируем это ...
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
... сначала кажется, что все стало еще хуже. Но посмотрите внимательно: на самом деле этап компиляции прошел отлично. Это просто компоновщик, который теперь жалуется на столкновение символов и сообщает нам место (исходный файл и строку), где это происходит. И, как мы видим, эти символы не имеют префикса.
Посмотрим на таблицы символов с помощью утилиты 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
Итак, теперь перед нами стоит задача добавить префикс этих символов в какой-нибудь непрозрачный двоичный файл. Да, я знаю, что в этом примере у нас есть источники и мы можем изменить это там. Но пока предположим, что у вас есть только эти файлы .o или .a (на самом деле это просто набор .o ).
objcopy спешит на помощь
Нам особенно интересен один инструмент: objcopy.
objcopy работает с временными файлами, поэтому мы можем использовать его, как если бы он работал на месте. Есть одна опция / операция, называемая --prefix-symbols, и у вас есть 3 предположения, что она делает.
Так что давайте бросим этого парня в наши упрямые библиотеки:
nm показывает нам, что это работало:
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
Давайте попробуем связать все это:
И действительно, это сработало:
example/ex03/ $ ./foobar foo: spam = 0, eggs = 0.000000 bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000
Теперь я оставляю читателю в качестве упражнения для реализации инструмента / скрипта, который автоматически извлекает символы библиотеки с помощью nm , записывает файл заголовка оболочки структуры.
/* wrapper header wrapper_foo.h for foo.h */ #define spam foo_spam #define eggs foo_eggs /* ... */ #include <foo.h> #undef spam #undef eggs /* ... */
и применяет префикс символа к объектным файлам статической библиотеки с помощью objcopy .
А как насчет общих библиотек?
В принципе то же самое можно сделать и с разделяемыми библиотеками. Однако общие библиотеки, как следует из названия, являются общими для нескольких программ, поэтому возиться с общей библиотекой таким образом - не такая уж хорошая идея.
Обертку для батута не обойдется. Хуже того, вы не можете связываться с разделяемой библиотекой на уровне объектного файла, а вынуждены выполнять динамическую загрузку. Но это заслуживает отдельной статьи.
Оставайтесь с нами, и удачного кодирования.
источник
objcopy
.objcopy --prefix-symbols
... +1!Это не просто расширение современных компиляторов; текущий стандарт C также требует, чтобы компилятор поддерживал достаточно длинные внешние имена. Я забыл точную длину, но сейчас это примерно 31 символ, если я правильно помню.
Тогда вы застряли. Пожаловаться автору библиотеки. Однажды я столкнулся с такой ошибкой, когда пользователи моего приложения не могли создать его на Debian из-за
libSDL
связывания Debianlibsoundfile
, которое (по крайней мере, в то время) ужасно загрязняло глобальное пространство имен такими переменными, какdsp
(я не шучу!). Я пожаловался Debian, и они исправили свои пакеты и отправили исправление в апстрим, где, как я полагаю, оно было применено, поскольку я никогда больше не слышал об этой проблеме.Я действительно считаю, что это лучший подход, потому что он решает проблему для всех . Любой локальный взлом, который вы сделаете, оставит проблему в библиотеке для следующего неудачливого пользователя, с которым он столкнется и снова будет бороться.
Если вам действительно нужно быстрое исправление, и у вас есть исходный код, вы можете добавить кучу
-Dfoo=crappylib_foo -Dbar=crappylib_bar
и т. Д. В make-файл, чтобы исправить это. Если нет, используйтеobjcopy
найденное вами решение.источник
Если вы используете GCC, переключатель компоновщика --allow-multiple-definition - удобный инструмент для отладки. Это заставляет компоновщика использовать первое определение (а не ныть по этому поводу). Подробнее об этом здесь .
Это помогло мне во время разработки, когда у меня есть исходный код для библиотеки, поставляемой поставщиком, и по той или иной причине мне нужно отследить библиотечную функцию. Переключатель позволяет скомпилировать и связать локальную копию исходного файла и по-прежнему ссылаться на неизмененную статическую библиотеку поставщика. Не забудьте выдернуть переключатель из символов создания, когда путешествие на поиски завершится. Код выпуска отгрузки с намеренными конфликтами пространства имен подвержен ошибкам, включая непреднамеренные конфликты пространств имен.
источник