WedX - журнал о программировании и компьютерных науках

Ошибка ссылки g++ — почему работает обходной путь


Я работаю над большим проектом. И теперь столкнулись с ошибкой ссылки.
Эту ошибку можно избежать с помощью обходного пути, но я не могу понять, почему он работает.

Вот структура файла, связанная с моей проблемой:

project
  |-package_a
    |--a.cpp
    |--...
  |-package_b
    |--b.cpp
    |--c.cpp
    |--...
  |-package_others


Все *.o в package_a будут упакованы в a.a, а *.o в package_b будут упакованы в b.a

"g++ -o exec -Bstatic b.a a.a ..." используется для создания двоичного файла.

В package_b/b.cpp я добавил функцию foo().
И в package_a/a.cpp я использовал эту функцию.

Но здесь я получаю сообщение об ошибке ссылки, говорящее о неопределенной ссылке foo () в a.o
Я могу проверить (с помощью objdump), что foo() уже находится в b.o.

Изменив команду ссылки на "g++ -o exec -Bstatic a.a b.a ...", можно успешно построить двоичный файл. Теперь я понимаю, что компоновщик заботится о порядке в списке компоновки. Но, пожалуйста, поймите, что это большой проект. У меня нет разрешения изменять конфигурацию проекта, поэтому необходимо сохранить исходный порядок ссылок.

Затем я добавил фиктивную функцию bar() в package_b/c.cpp, которая ничего не делает, кроме вызова foo(), после чего исходный "g++ -o exec -Bstatic b.a a.a ..." будет работать без ошибок связи.

Может ли кто-нибудь показать мне, почему просто добавление фиктивной функции в тот же пакет будет работать в этом случае?

Я использую g++ 4.4.4 и Linux 2.6.18-194.el5.

Любой комментарий будет оценен


Ответы:


1

Это нормально. При компоновке компоновщик просматривает список объектных файлов, находя неопределенные ссылки, которые затем удовлетворяются другими объектными файлами/библиотеками, идущими после.

Вы можете изменить это поведение либо

  • включая один из архивов дважды, как в

    g++ -o exec a.a b.a a.a
    
  • используя конструкцию -(

    g++ -o exec -( a.a b.a -)
    

Но, пожалуйста, поймите, что это большой проект. У меня нет разрешения на изменение конфигурации проекта, поэтому необходимо сохранить первоначальный порядок ссылок.

Не повезло... Может быть, менеджер или кто-то еще просто не хочет, чтобы вы использовали функции в b из a.

Затем я добавил фиктивную функцию bar() в package_b/c.cpp, которая ничего не делает, кроме как просто вызывает foo(), после чего оригинальный "g++ -o exec -Bstatic b.a a.a..." будет выполняться без какой-либо ошибки ссылки.

Возможно, уже была ссылка на другую функцию package_b/c.cpp, и компоновщик взял с собой bar() (поскольку они находятся в одном файле), а эта ссылалась на foo(), которая впоследствии тоже была включена в вывод. Это удалось, потому что foo тоже был в b.a.

07.11.2011
  • Could be that another function of package_b/c.cpp was already referenced, and the linker took bar() with it (because they are in the same file) and this referenced foo(), which was subsequently included in the output, too. It succeeded, because foo was in b.a too. ‹br/›У меня все еще есть сомнения по этому поводу, вы имеете в виду, что на package_b/c.cpp уже ссылались до обработки компоновщика a.a? но как не может быть ошибки ссылки, когда она ссылается на эти символические символы в package_b/c.cpp? 07.11.2011
  • @ user1033573: Когда объектный файл ссылается на символ из текущей библиотеки/архива, он просто работает, вам не нужно делать ничего особенного. Или я неправильно понял ваш вопрос? 07.11.2011
  • Вы сказали, что другая функция в c.cpp, скажем, ссылается на bar2(), и компоновщик взял с ней bar(). Насколько я понимаю, это происходит до того, как компоновщик передаст a.a., верно? Но почему компоновщик не жалуется на bar2(), не определено, так как b.a все еще отстает в последовательности ссылок. 07.11.2011
  • @Yike.Wang: Это происходит во время обработки архива b.a. Может быть файл, содержащий main, могут быть какие-то неопределенные символы из предыдущих аргументов. В любом случае, компилятор видит c.cpp (в b.a) и foo.cpp (в b.a), по какой-то причине нуждается в c.cpp, который, в свою очередь, требует foo.cpp. a.a не имеет к этому никакого отношения, за исключением того, что позже он выигрывает от того, что foo уже включен в вывод. 08.11.2011
  • Я просто столкнулся с этим. Я был озадачен, почему связывание с *.a не работает, а с *.a *.a нет. Спасибо за объяснение. Я предполагаю, что это побочный эффект использования архивов, в котором некоторые объектные файлы могут быть нужны немедленно, а другие нет. 23.01.2014

  • 2

    Вы можете прочитать о том, как работают компоновщики. Кстати, флаг -Bstatic не нужен, потому что архивы объектных файлов .a. компонуются только статически (как если бы список объектных файлов, содержащихся в .a, был указан в командной строке вместо .a).

    Кроме того, вы всегда можете обернуть список архивов для связывания с параметрами --start-group/ --end-group, чтобы компоновщик сканировал список архивов несколько раз, чтобы не требовался порядок архивов (как это делает MS VC++):

    g++ -o exec -Wl,--start-group a.a b.a -Wl,--end-group
    

    См. man ld:

       -( archives -)
       --start-group archives --end-group
           The archives should be a list of archive files.  They may be either
           explicit file names, or -l options.
    
           The specified archives are searched repeatedly until no new
           undefined references are created.  Normally, an archive is searched
           only once in the order that it is specified on the command line.
           If a symbol in that archive is needed to resolve an undefined
           symbol referred to by an object in an archive that appears later on
           the command line, the linker would not be able to resolve that
           reference.  By grouping the archives, they all be searched
           repeatedly until all possible references are resolved.
    
           Using this option has a significant performance cost.  It is best
           to use it only when there are unavoidable circular references
           between two or more archives.
    
    07.11.2011
  • Это не то, как линкеры работают в целом. VC++ может обрабатывать зависимости в любом порядке, и я не могу придумать ни одной причины, по которой GCC не может делать то же самое. 07.11.2011
  • VС++ в этом отношении более щадящий. Компоновщики UNIX могут работать так же, как VC++, но об этом нужно попросить, см. мое обновление. 07.11.2011
  • @maxim-yegorushkin Было бы неплохо иметь возможность сделать это тоже. в любом порядке удобно, но медленнее. Циклические зависимости всегда являются признаком плохого дизайна кода или организации библиотеки. 07.11.2011
  • @EddyPronk У ld уже нет этой опции? По умолчанию он делает один проход по списку архивов, с --start-group/ --end-group он делает несколько проходов. 30.09.2014

  • 3

    GCC, в отличие от компоновщика Visual-C++, требует, чтобы статические библиотеки поставлялись в таком порядке, чтобы ссылки были определены до их использования. Не спрашивайте меня почему, но вам всегда придется проверять, что вы перечисляете файлы, которые должны быть связаны в правильном порядке с помощью GCC.

    здесь есть подробное объяснение.

    07.11.2011
  • Спасибо, Pollex, это объяснило первое решение, но не второе (фиктивная функция, добавленная в c.cpp) 07.11.2011
  • Поддержка циклических зависимостей имеет свою цену: вам либо нужен двухпроходный алгоритм для решения всех символов (хуже производительность), либо вам нужно сохранять обратные ссылки на места, где были нерешенные символы (худший код); ИМХО польза только для некоторых мала, а для большинства ничтожна. ИМХО2, циклическая зависимость сомнительна: если вам все равно нужны оба, почему бы не сделать их одним пакетом. 07.11.2011

  • 4

    Когда вы используете функцию из статической библиотеки, вы должны в командной строке сначала поместить файл, из которого используется функция, а затем библиотеку, в которой функция определена. В противном случае, если вы поместите определение первым, gcc (или, точнее, ld) отбросит «неиспользуемую» функцию. Так работает gcc, извините.

    07.11.2011
    Новые материалы

    Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что это выглядит сложно…
    Просто начните и учитесь самостоятельно Я хотел выучить язык программирования MVC4, но не мог выучить его раньше, потому что он кажется мне сложным, и я бросил его. Это в основном инструмент..

    Лицензии с открытым исходным кодом: руководство для разработчиков и создателей
    В динамичном мире разработки программного обеспечения открытый исходный код стал мощной парадигмой, способствующей сотрудничеству, инновациям и прогрессу, движимому сообществом. В основе..

    Объяснение документов 02: BERT
    BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

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

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..


    Для любых предложений по сайту: [email protected]