У вас может быть один вариант проблемы или другой вариант, в зависимости от фактов, актуальность которых вы еще не рассмотрели. Или, возможно, у вас есть сочетание того и другого, поэтому я рассмотрю решение для каждого варианта.
Вы должны быть знакомы с природой библиотек stati c и тем, как они используются при связывании, как указано здесь
Вариант символов Superflous Globals
Вот пара исходных файлов и файл заголовка:
один. cpp
#include <onetwo.h>
int clash = 1;
int get_one()
{
return clash;
}
два. cpp
#include <onetwo.h>
int get_two()
{
return 2;
}
onetwo.h
#pragma once
extern int get_one();
extern int get_two();
Они были встроены в статическую c библиотеку libonetwo.a
$ g++ -Wall -Wextra -pedantic -I. -c one.cpp two.cpp
$ ar rcs libonetwo.a one.o two.o
, предполагаемый API которой определен в onetwo.h
Аналогично, были созданы некоторые другие исходные файлы и заголовок. в c библиотеку libfourfive.a
, предполагаемый API которой определен в fourfive.h
четыре. cpp
#include <fourfive.h>
int clash = 4;
int get_four()
{
return clash;
}
пять. cpp
#include <fourfive.h>
int get_five()
{
return 5;
}
fourfive.h
#pragma once
extern int get_four();
extern int get_five();
А вот исходный код программы, которая зависит от обеих библиотек:
прогр. cpp
* 1 057 *
, который мы пытаемся построить следующим образом:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(four.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(one.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
сталкивается с конфликтом имен для символа clash
, потому что он глобально определен в двух объектных файлах, которые требуются для связывания, one.o
и four.o
:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 10 entries:
9: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 _Z7get_twov
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z8get_fourv
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 10 entries:
9: 0000000000000000 15 FUNC GLOBAL DEFAULT 1 _Z8get_fivev
Символ проблемы clash
не упоминается в нашем собственном коде prog.(cpp|o)
. Вы задались вопросом:
Есть ли способ сказать компоновщику «жаловаться на несколько определений, только если используется символ»?
Нет, нет, но это несущественный. one.o
не был бы извлечен из libonetwo.a
и связан с программой, если бы компоновщик не нуждался в этом для разрешения некоторого символа. Это нужно для разрешения get_one
. Точно так же он связал только four.o
, потому что это необходимо для разрешения get_four
. Итак, противоречащие определения clash
находятся в связи. И хотя prog.o
не использует clash
, он использует get_one
, который использует clash
и который намеревается использовать определение clash
в one.o
. Точно так же prog.o
использует get_four
, который использует clash
и намеревается использовать другое определение в four.o
.
Даже если clash
был также не использовался каждой библиотекой как и программа, тот факт, что он определен в нескольких объектных файлах, которые должны быть связаны с программой, означает, что программа будет содержать несколько его определений, и только --allow-multiple-definitions
позволит это.
В этом свете вы также увидите, что:
Или, альтернативно, [есть ли способ] сказать это: «из библиотеки AB C игнорировать символ XYZ».
в генерал не летает. Если бы мы могли сказать компоновщику игнорировать (скажем) определение clash
в four.o
и разрешать символ везде до определения в one.o
(единственный другой кандидат), тогда get_four()
вернет 1 вместо 4 в наша программа. Фактически это эффект --allow-multiple-definitions
, поскольку он приводит к использованию первого определения в привязке.
Изучив исходный код libonetwo.a
(или libfourfive.a
), мы можем довольно уверенно определите root причину проблемы. Символ clash
оставлен с внешней связью, где требуется только внутренняя связь, поскольку он не объявлен в связанном файле заголовка и нигде не упоминается в библиотеке, кроме файла, в котором он определен. В исходных файлах с нарушением правил должно быть написано:
one_good. cpp
#include <onetwo.h>
namespace {
int clash = 1;
}
int get_one()
{
return clash;
}
four_good. cpp
#include <fourfive.h>
namespace {
int clash = 4;
}
int get_four()
{
return clash;
}
и все было бы хорошо:
$ g++ -Wall -Wextra -pedantic -I. -c one_good.cpp four_good.cpp
$ readelf -s one_good.o four_good.o | egrep '(File|Symbol|OBJECT|FUNC)'
File: one_good.o
Symbol table '.symtab' contains 11 entries:
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 _ZN12_GLOBAL__N_15clashE
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: four_good.o
Symbol table '.symtab' contains 11 entries:
5: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 _ZN12_GLOBAL__N_15clashE
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z8get_fourv
$ g++ -o prog prog.o one_good.o four_good.o
$./prog; echo $?
5
Так как переписывать исходный код подобным образом нельзя, мы должны изменить объектные файлы с тем же эффектом. Инструмент для этого: objcopy
.
$ objcopy --localize-symbol=clash libonetwo.a libonetwo_good.a
Эта команда имеет тот же эффект, что и выполнение:
$ objcopy --localize-symbol=clash orig.o fixed.o
для каждого из объектных файлов libonetwo(orig.o)
для вывода фиксированного объектного файла fixed.o
и архивации всех fixed.o
файлов в новую c библиотеку libonetwo_good.a
. И Эффект --localize-symbol=clash
для каждого объектного файла заключается в изменении связи символа clash
, если он определен, с внешнего (GLOBAL
) на внутреннее (LOCAL)
:
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|OBJECT|FUNC)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT LOCAL DEFAULT 3 clash
10: 0000000000000000 16 FUNC GLOBAL DEFAULT 1 _Z7get_onev
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 10 entries:
Теперь компоновщик не видит LOCAL
определение clash
в libonetwo_good.a(one.o)
.
Этого достаточно, чтобы предотвратить ошибку множественного определения, но поскольку libfourfive.a
имеет тот же дефект, мы исправим и его :
$ objcopy --localize-symbol=clash libfourfive.a libfourfive_good.a
И затем мы можем повторно связать prog
, используя фиксированные библиотеки.
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5
Вариант тупика глобальных символов
В этом сценарии Источники и заголовки для libonetwo.a
:
один. cpp
#include <onetwo.h>
#include "priv_onetwo.h"
int inc_one()
{
return inc(clash);
}
два. cpp
#include <onetwo.h>
#include "priv_onetwo.h"
int inc_two()
{
return inc(clash + 1);
}
priv_onetwo. cpp
#include "priv_onetwo.h"
int clash = 1;
int inc(int i)
{
return i + 1;
}
priv_onetwo.h
#pragma once
extern int clash;
extern int inc(int);
onetwo.h
#pragma once
extern int inc_one();
extern int inc_two();
А для libfourfive.a
это:
четыре. cpp
#include <fourfive.h>
#include "priv_fourfive.h"
int dec_four()
{
return dec(clash);
}
пять. cpp
#include <fourfive.h>
#include "priv_fourfive.h"
int dec_five()
{
return dec(clash + 1);
}
priv_fourfive. cpp
#include "priv_fourfive.h"
int clash = 4;
int dec(int i)
{
return i - 1;
}
priv_fourfive. h
#pragma once
extern int clash;
extern int dec(int);
fourfive.h
#pragma once
extern int dec_four();
extern int dec_five();
Каждая из этих библиотек построена с некоторыми общими внутренними компонентами, определенными в исходном файле - (priv_onetwo.cpp
| priv_fourfive.cpp
) - и эти внутренние компоненты глобально объявлены для построения библиотеки через частный заголовок - (priv_onetwo.h
| priv_fourfive.h
) - который не распространяется с библиотекой. Это недокументированные символы, но, тем не менее, они доступны компоновщику.
Теперь в каждой библиотеке есть два файла, которые делают неопределенные (UND
) ссылки на глобальный символ clash
, который определен в другом файле:
$ readelf -s libonetwo.a libfourfive.a | egrep '(File|Symbol|OBJECT|FUNC|clash)'
File: libonetwo.a(one.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 _Z7inc_onev
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libonetwo.a(two.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 _Z7inc_twov
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libonetwo.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash
10: 0000000000000000 19 FUNC GLOBAL DEFAULT 1 _Z3inci
File: libfourfive.a(four.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 23 FUNC GLOBAL DEFAULT 1 _Z8dec_fourv
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libfourfive.a(five.o)
Symbol table '.symtab' contains 13 entries:
9: 0000000000000000 26 FUNC GLOBAL DEFAULT 1 _Z8dec_fivev
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash
File: libfourfive.a(priv_fourfive.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash
10: 0000000000000000 19 FUNC GLOBAL DEFAULT 1 _Z3deci
Наш источник программы на этот раз:
prog. cpp
#include <onetwo.h>
#include <fourfive.h>
int main()
{
return inc_one() + dec_four();
}
и:
$ g++ -Wall -Wextra -pedantic -I. -c prog.cpp
$ g++ -o prog prog.o -L. -lonetwo -lfourfive
/usr/bin/ld: ./libfourfive.a(priv_fourfive.o):(.data+0x0): multiple definition of `clash'; ./libonetwo.a(priv_onetwo.o):(.data+0x0): first defined here
collect2: error: ld returned 1 exit status
еще раз clash
многократно определено. Чтобы разрешить inc_one
в main
, компоновщику требуется one.o
, что обязывает его разрешить inc
, что делает его необходимым priv_onetwo.o
, который содержит первое определение clash
. Чтобы разрешить dec_four
в main
, компоновщику требуется four.o
, что обязывает его разрешить dec
, что требует priv_fourfive.o
, которое содержит конкурирующее определение clash
.
В этом случае это не ошибка кодирования ни в одной из библиотек, в которых clash
есть внешняя связь. Он должен иметь внешнюю связь. Локализация определения clash
с objcopy
в любом из libonetwo.a(priv_onetwo.o)
или libfourfive.a(priv_fourfive.o)
не будет работать. Если мы сделаем это, компоновка будет успешной, но выведет программу с ошибками, потому что компоновщик разрешит clash
одно сохранившееся GLOBAL
определение из другого объектного файла: тогда dec_four()
вернет 0 вместо 3 в программе, dec_five()
вернет 1, а не 4; иначе inc_one()
вернет 5, а inc_two()
вернет 6. И если мы локализуем оба определения, тогда определение clash
не будет найдено в связи prog
, чтобы удовлетворить ссылки в one.o
или four.o
, и он не будет работать для неопределенной ссылки на clash
На этот раз objcopy
снова приходит на помощь, но с другим вариантом 1 :
$ objcopy --redefine-sym clash=clash_onetwo libonetwo.a libonetwo_good.a
Результатом этой команды является создание новой c библиотеки libonetwo_good.a
, содержащей новые объектные файлы, которые попарно идентичны файлам в libonetwo.a
, за исключением того, что символ clash
был везде заменен на clash_onetwo
:
$ readelf -s libonetwo_good.a | egrep '(File|Symbol|clash)'
File: libonetwo_good.a(one.o)
Symbol table '.symtab' contains 13 entries:
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash_onetwo
File: libonetwo_good.a(two.o)
Symbol table '.symtab' contains 13 entries:
10: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND clash_onetwo
File: libonetwo_good.a(priv_onetwo.o)
Symbol table '.symtab' contains 11 entries:
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 2 clash_onetwo
Мы сделаем то же самое с libfourfive.a
:
$ objcopy --redefine-sym clash=clash_fourfive libfourfive.a libfourfive_good.a
Теперь нам хорошо go еще раз :
$ g++ -o prog prog.o -L. -lonetwo_good -lfourfive_good
$ ./prog; echo $?
5
Из двух решений используйте исправление для Вариант символов Superflous Globals , если у вас есть избыточные глобалы, хотя исправление для The Global Символы Вариант тупика также будет работать. Никогда не желательно вмешиваться в объектные файлы между компиляцией и компоновкой; это может быть только неизбежным или меньшим из зол. Но если вы собираетесь вмешиваться в них, локализация глобального символа, который никогда не должен был быть глобальным, будет более очевидным вмешательством, чем изменение имени символа на имя, не имеющее происхождения в исходном коде.
[1] Не забывайте, что если вы хотите использовать objcopy
с любым аргументом опции, который является символом в объектном файле C ++, вы должны использовать искаженное имя идентификатора C ++. чем соответствует символу. В этом демонстрационном коде случается, что искаженное имя идентификатора C ++ clash
также clash
. Но если, например, полный идентификатор был onetwo::clash
, его искаженное имя было бы _ZN6onetwo5clashE
, как сообщает nm
или readelf
. И наоборот, если вы хотите использовать objcopy для замены _ZN6onetwo5clashE
в объектном файле на символ, который будет выглядеть как onetwo::klash
, тогда этот символ будет _ZN6onetwo5klashE
.