Страницы

Поиск по вопросам

пятница, 14 февраля 2020 г.

Как отключить предупреждение “multiple definition of symbol”

#cpp #c #g++ #компоновщик #ld


Изучаю работу редактора связей и специально для этого написал программу, в которой
два раза появляется один и тот же символ. Хочу посмотреть на поведение. Однако, редактор
связей выдает предупреждение: 


  multiple definition of symbol


и отказывается дальше работать. Собираю через g++, есть подозрения, что это g++ выставляет
какой-то флаг для ld, чтобы редактор связей останавливал работу в таких случаях. Насколько
я знаю,такие ситуации допускаются, и  есть определенные правила для их разрешения.
Как заставить ld игнорировать множественное определение символа?
    


Ответы

Ответ 1



Насчет "такие ситуации допускаются и есть определенные правила": ситуации с множественными определениями одной и той же внешней сущности являются ошибками и в С++, и в С, хотя исторически многие реализации С в качестве расширения разрешают множественные внешние определения одной и то же переменной без инициализатора (формально это запрещено в С). Вот и все правила. А если вы хотите подавить ошибки для конкретного символа на уровне исходного кода - то просто объявите его weak, т.е. с __attribute__((weak)) А также можно отключить ошибку глобально, на уровне командной строки ld через опцию -z muldefs или ее синоним --allow-multiple-definition (т.е. если вы используете gcc для неявного вызова линкера, то -Wl,-z,muldefs или -Wl,--allow-multiple-definition). http://coliru.stacked-crooked.com/a/9e49a6024baf2589

Ответ 2



Всё, что написано далее, относится только к реализации системы компиляции GNU. Вы имеете 2 strong символа. То, что глобальная функция является strong символом, это очевидно. Доказательство того, что глобальная переменная без инициализатора (только в случае g++ при компиляции *.cpp-файлов) является strong символом, а не common или weak символом: Допустим, имеем 2 файла: f1.cpp и f2.cpp. В файле f1.cpp определена функция с сигнатурой int some(void), которая mangled (коверкается компилятором) в _Z4somev. В файле f2.cpp определена глобальная непроинициализированная переменная _Z4somev следующим образом: int _Z4somev;, то есть явно мы ей не присвоили никакое значение. Получаем relocatable object files (f1.o, f2.o): g++ -c f1.cpp; g++ -c f2.cpp. Команда readelf -s f2.o выдаст для символа глобальной переменной _Z4somev: а) В колонке Ndx не 'COM', то есть символ не является common; б) В колонке Bind не 'WEAK', то есть символ не является weak. Команда nm f2.o выдаст для символа глобальной переменной _Z4somev следующее: 0000000000000000 B _Z4somev. Важно то, что во второй колонке 'B'. Если бы символ был weak, то во второй колонке было бы 'V'. Если бы символ был common, то во второй колонке было бы 'C'. Если бы мы проделали те же самые действия, но в файле f2.cpp определили бы переменную _Z4somev следующим образом: int _Z4somev __attribute__((weak));, то в колонке Bind утилита readelf показала бы значение 'WEAK' для этого символа, а утилита nm показала бы значение 'V' для этого символа, то есть символ переменной _Z4somev является weak. Если переименовать файлы f1.cpp и f2.cpp в f1.c и f2.c соответственно, а название переменной изменить из _Z4somev в some (так как название функции some не будет mangled при вызове gcc), то если выполнить gcc -c f1.c; gcc -c f2.c; readelf -s f2.o; nm f2.o, то для символа глобальной переменной в колонке Ndx утилита readelf отобразит значение COM, что значит, что символ является common, а в колонке Bind - значение GLOBAL, что значит, что символ не является weak; а утилита nm во второй колонке отобразит C, что значит, что символ является common и не является weak. Если в коде для глобальной не extern переменной программист не добавил инициализатор, то символ этой переменной: При компиляции *.c-файлов с помощью gcc: а) Становится common; б) Не становится weak; в) Не становится strong. При компиляции *.cpp-файлов с помощью g++: а) Становится strong; б) Не становится weak; в) Не становится common. Шаги при компиляции, относящиеся к вопросу: Шаг 1. При компиляции с помощью g++ *.cpp, если компилятор встречает глобальную переменную без явной инициализации программистом (пример, int some;), то компилятор неявно пишет инициализатор, в котором переменной присваивается значение по умолчанию (например, int some = 0;). При компиляции с помощью gcc *.c, компилятор не будет в этих случаях неявно добавлять инициализатор. Понятно, что даже даже если ни программист, ни компилятор не добавили инициализатор, глобальная переменная всё равно проинициализируется значением 0, так как секция .bss заполняется нулями загрузчиком исполняемых файлов. Но синтаксическое наличие инициализатора, не важно кем добавленного, программистом или компилятором, влияет на шаг №2. Шаг 2. Этот шаг не отличается от того, каким образом была вызвана компиляция: gcc *.c или же g++ *.cpp. а) Если атрибут __attribute__((weak)) при объявлении переменной имеется, то символ является weak (и не является strong и не является common); б) Если атрибута __attribute__((weak)) нет, то: б.1) Если инициализатор есть (не важно, добавленный программистом или же компилятором), то глобальная переменная является strong (но не является weak и не является common); б.2) Если инициализатора нет, то глобальная переменная является common (но не является weak и не является strong). Компилятор (cc1, запускаемый при gcc -c *.c; cc1plus, запускаемый при g++ -c *.cpp) определяет то, каким будет символ: strong, weak или common и эту информацию далее передаёт программе as, которая создаёт файлы *.o и записывает в них информацию о типе символов, которую мы можем просмотреть с помощью утилит readelf и nm. Линкер (ld; вызывается программой collect2, которая в свою очередь вызывается программой gcc/g++, что видно с ключом -v) определяет то, какой именно символ, weak, strong или common, появляется в файлах *.o, основываясь на информации из таблицы символов, хранящихся в этих файлах (секция .symtab), которую мы просматривали с помощью утилит readelf и nm. Далее линкер ld использует следующие правила (при разрешении символьных ссылок), основываясь на полученной информации: Множество strong символов с одинаковым именем запрещены, поэтому линковщик выдаст ошибку. Если имеется 1 strong символ и несколько weak/common символов с одним именем, то линковщик выбирает strong символ. Если имеется несколько weak/common символов с одним именем, то линкер выбирает любой из них (чтобы предотвратить такое поведение, необходимо в gcc/g++ передать флаг -fno-common или использовать опцию -Werror). Ввиду того, что вопрос касался проблемы несогласованности полученного теоретического материала с практическими тестами этого материала, данный ответ позволяет аннигилировать эту несогласованность. Это плохо, когда имеется множество глобальных символов с одним и тем же именем.

Комментариев нет:

Отправить комментарий