Почему порядок «-l» в gcc имеет значение?

Я пытаюсь скомпилировать программу, которая использует библиотеку udis86 . Фактически, я использую примерную программу, приведенную в руководстве пользователя библиотеки. Но при компиляции он дает ошибку. Я получаю ошибки:

example.c:(.text+0x7): undefined reference to 'ud_init' example.c:(.text+0x7): undefined reference to 'ud_set_input_file' . . example.c:(.text+0x7): undefined reference to 'ud_insn_asm' 

Команда, которую я использую:

 $ gcc -ludis86 example.c -o example 

как указано в руководстве пользователя.

Очевидно, что компоновщик не может связывать библиотеку libudis. Но если я изменю свою команду на:

 $ gcc example.c -ludis86 -o example 

Он начинает работать. Так может ли кто-нибудь объяснить, в чем проблема с первой командой?

    Так как алгоритм связывания, используемый GNU-компоновщиком, работает (как минимум, когда речь идет о связывании статических библиотек). Линкером является однопроходный линкер, и он не пересматривает библиотеки, как только они были замечены.

    Библиотека представляет собой коллекцию (архив) объектных файлов. Когда вы добавляете библиотеку с использованием опции -l , компоновщик не безоговорочно принимает все объектные файлы из библиотеки. Он принимает только те файлы объектов, которые в настоящее время необходимы , то есть файлы, которые разрешают некоторые нерешенные (ожидающие) символы. После этого компоновщик полностью забывает об этой библиотеке.

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

    Итак, если вы включили некоторую библиотеку с помощью -l , компоновщик использует эту библиотеку для разрешения как можно большего количества текущих ожидающих символов, а затем полностью забывает об этой библиотеке. Если позже выяснится, что теперь он нуждается в дополнительных объектных файлах из этой библиотеки, компоновщик не будет «возвращаться» к этой библиотеке для извлечения этих дополнительных объектных файлов. Уже слишком поздно.

    По этой причине всегда полезно использовать опцию -l конце строки командной строки компоновщика, так что к тому времени, когда линкер доберется до этого -l он может надежно определить, какие файлы объектов ему нужны и которые им не нужны , Размещение опции -l как самого первого параметра в компоновщике вообще не имеет никакого смысла: в самом начале список ожидающих символов пуст (или, точнее, состоит из main символа), что означает, что компоновщик не будет вообще ничего не беру из библиотеки.

    В вашем случае ваш объектный файл example.o содержит ссылки на символы ud_init , ud_set_input_file и т. Д. ud_set_input_file должен сначала получить этот объектный файл. Он добавит эти символы в список ожидающих символов. После этого вы можете использовать -l для добавления вашей библиотеки: -ludis86 . Компонент будет искать вашу библиотеку и брать все, что разрешает эти ожидающие символы.

    Если вы -ludis86 параметр -ludis86 в командной строке, компоновщик будет эффективно игнорировать вашу библиотеку, так как вначале он не знает, что ему понадобятся ud_init , ud_set_input_file и т. Д. Позже, при обработке example.o он обнаружит эти символы и добавьте их в список ожидающих символов. Но эти символы останутся неразрешенными до конца, поскольку -ludis86 уже обработан (и эффективно игнорируется).

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

    Я снова попал в эту проблему . Итог заключается в том, что инструменты gnu не всегда будут «искать назад» в списке библиотек для устранения недостающих символов. Легкие исправления являются следующими:

    1. Просто укажите libs и objs в порядке зависимости (как вы обнаружили выше)

    2. ИЛИ если у вас есть циклическая зависимость (где libA ссылается на функцию в libB, но libB ссылается на функцию в libA), то просто укажите libs в командной строке дважды. Это то, что предлагает справочная страница. Например

       gcc foo.c -lfoo -lbar -lfoo 
    3. Используйте параметры -( и -) чтобы указать группу архивов, имеющих такие циклические зависимости. Посмотрите руководство GNU-linker для --start-group и --end-group . Подробнее см. Здесь .

    Когда вы используете опцию 2 или 3, вы, вероятно, вводите стоимость выполнения ссылок. Если у вас нет такой ссылки, это может быть неважно.

    Или используйте повторное сканирование

    из стр. 41 Руководства для библиотек и библиотек Oracle Solaris 11.1 :

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

     $ cc -o prog .... -lA -lB -lC -lA -lB -lC -lA 

    Определение и техническое обслуживание повторяющихся архивных спецификаций может быть утомительным.

    Опция -z rescan-now делает этот процесс более простым. Опция -z rescan-now обрабатывается редактором ссылок сразу же, когда опция встречается в командной строке. Все архивы, обработанные из командной строки до этой опции, сразу же обрабатываются. Эта обработка пытается найти дополнительные элементы архива, которые разрешают ссылки на символы. Повторное сканирование этого архива продолжается до тех пор, пока не произойдет переполнение списка архивов, в котором не будут извлечены новые члены. Предыдущий пример можно упростить следующим образом.

     $ cc -o prog .... -lA -lB -lC -z rescan-now 

    В качестве альтернативы, параметры повторного сканирования -z и -z могут быть использованы для группировки взаимозависимых архивов вместе в группу архива. Эти группы обрабатываются редактором ссылок сразу же, когда в командной строке встречается разделительный разделитель. Архивы, найденные внутри группы, перерабатываются в попытке найти дополнительные элементы архива, которые разрешают ссылки на символы. Повторное сканирование этого архива продолжается до тех пор, пока не произойдет прохождение группы архива, в которой не будут извлекаться новые члены. Используя архивные группы, предыдущий пример можно записать следующим образом.

     $ cc -o prog .... -z rescan-start -lA -lB -lC -z rescan-end 
    Давайте будем гением компьютера.