Как линкеры решают, какие части библиотек включать? - PullRequest
9 голосов
/ 04 апреля 2009

Предположим, что библиотека A имеет a () и b (). Если я свяжу свою программу B с A и вызову a (), включится ли b () в двоичный файл? Видит ли компилятор, вызывает ли какая-либо функция в программе вызов b () (возможно, a (), вызывает b () или другой lib вызывает b ())? Если так, как компилятор получает эту информацию? Если нет, разве это не большая трата окончательного размера компиляции, если я ссылаюсь на большую библиотеку, но использую только незначительную функцию?

Ответы [ 9 ]

9 голосов
/ 04 апреля 2009

Взгляните на оптимизация времени соединения . Это обязательно зависит от поставщика. Это также будет зависеть от того, как вы создадите свои двоичные файлы. MS-компиляторы (по крайней мере, начиная с 2005 года) предоставляют нечто, называемое Функциональное связывание - это еще один способ удаления ненужных символов. В этой публикации объясняется, как этого можно добиться с помощью GCC (это старая версия, GCC, должно быть, перешла, но содержание относится к вашему вопросу).

Также взгляните на реализацию LLVM (и раздел примеров).

Предлагаю вам также взглянуть на Линкеры и загрузчики Джона Левина - отличное чтение.

8 голосов
/ 04 апреля 2009

Это зависит.

Если библиотека является общим объектом или DLL, то все содержимое библиотеки загружается, но во время выполнения. Стоимость дополнительной памяти (надеюсь) компенсируется за счет совместного использования библиотеки (в действительности кодовых страниц) между всеми процессами в памяти, которые используют эту библиотеку. Это большая победа для чего-то вроде libc .so, меньше для myreallyobscurelibrary.so. Но вы, вероятно, не спрашиваете об общих объектах, действительно.

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

Задача компоновщика - создать готовый исполняемый файл, в котором нет оставшихся неопределенных импортированных символов. (Я лгу, конечно, если динамическое связывание разрешено, но потерпите меня.) Для этого он начинается с модулей, названных явно в командной строке ссылки (и, возможно, неявно в его конфигурации) и предполагает, что любой модуль named явно должен быть частью готового исполняемого файла. Затем он пытается найти определения для всех неопределенных символов.

Обычно именованные объектные модули ожидают получить символы из некоторой библиотеки, такой как libc.a.

В вашем примере у вас есть единственный модуль, который вызывает функцию a(), в результате чего компоновщик ищет модуль, который экспортирует a().

Вы говорите, что библиотека с именем A (в Unix, вероятно, libA.a) предлагает a() и b(), но вы не указываете как. Вы подразумевали, что a() и b() не звонят друг другу, как я предполагаю.

Если libA.a был построен из a.o и b.o, где каждая определяет соответствующую отдельную функцию, тогда компоновщик будет включать a.o и игнорировать b.o.

Однако, если libA.a включает ab.o, который определяет и a(), и b(), то он будет включать ab.o в ссылку, удовлетворяя потребность в a() и включая неиспользуемую функцию b() ,

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

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

2 голосов
/ 04 апреля 2009

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

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

1 голос
/ 04 апреля 2009

Эта лекция в Academic Earth дает довольно хороший обзор, о связях говорят около второй половины доклада, IIRC.

1 голос
/ 04 апреля 2009

Зависит от компоновщика.

например. В Microsoft Visual C ++ есть опция «Включить связывание на уровне функций», поэтому вы можете включить ее вручную.

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

0 голосов
/ 04 апреля 2009

Это зависит от компоновщика и от того, как была построена библиотека. Обычно библиотеки представляют собой комбинацию объектных файлов (библиотеки импорта являются основным исключением из этого). Старые компоновщики вставляли вещи в изображение выходного файла с гранулярностью объектных файлов, которые были помещены в библиотеку. Таким образом, если бы функция a() и функция b() находились в одном объектном файле, они оба были бы в выходном файле - даже если бы фактически была указана только одна из 2 функций.

Это причина, по которой вы часто будете видеть библиотечно-ориентированные проекты с политикой по одной функции C на исходный файл. Таким образом, каждая функция упакована в свой собственный объектный файл, и компоновщики без проблем извлекают только то, на что ссылаются.

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

0 голосов
/ 04 апреля 2009

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

$ cat foo.c
int main(){}

$ gcc -static foo.c

$ size
   text    data     bss     dec     hex filename
 452659    1928    6880  461467   70a9b a.out

# force linking of libz.a even though it isn't used
$ gcc -static foo.c -Wl,-whole-archive -lz -Wl,-no-whole-archive

$ size
   text    data     bss     dec     hex filename
 517951    2180    6844  526975   80a7f a.out
0 голосов
/ 04 апреля 2009

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

0 голосов
/ 04 апреля 2009

Это зависит от компоновщика, но в общем случае в конечный исполняемый файл включаются только те функции, которые на самом деле называются. Компоновщик работает, ища имя функции в библиотеке и затем используя код, связанный с именем.

Очень мало книг по линкерам, что странно, когда вы думаете, насколько они важны. Текст для хорошего можно найти здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...