Предположим, что в библиотеке A есть функции a() и b(). Если я свяжу свою программу B с A и вызову a(), будет ли b() включена в двоичный файл? Видит ли компилятор, вызывает ли какая-либо функция в программе b() (возможно, a() вызывает b() или другая библиотека вызывает b())? Если да, то как компилятор получает эту информацию? Если нет, не является ли это большой тратой окончательного размера компиляции, если я ссылаюсь на большую библиотеку, но использую только второстепенную функцию?
Как компоновщики решают, какие части библиотек включать?
Ответы (9)
Взгляните на оптимизацию времени ссылки. Это обязательно зависит от поставщика. Это также будет зависеть от того, как вы создаете свои двоичные файлы. Компиляторы MS (по крайней мере, начиная с 2005 г.) предоставляют нечто, называемое связывание на функциональном уровне. -- это еще один способ убрать символы, которые вам не нужны. В этом сообщении объясняется, как того же можно добиться с помощью GCC (это устарело, GCC должен мы пошли дальше, но содержание имеет отношение к вашему вопросу).
Также взгляните на реализацию LLVM (и раздел примеров).
Я предлагаю вам также взглянуть на компоновщики и загрузчики Джон Левайн - отличное чтение.
Это зависит.
Если библиотека является общим объектом или DLL, то все в библиотеке загружается, но во время выполнения. Стоимость дополнительной памяти (надеюсь) компенсируется за счет совместного использования библиотеки (на самом деле, кодовых страниц) между всеми процессами в памяти, которые используют эту библиотеку. Это большая победа для чего-то вроде libc.so, в меньшей степени для myreallyobscurelibrary.so. Но на самом деле вы, вероятно, не спрашиваете об общих объектах.
Статические библиотеки — это просто набор отдельных объектных файлов, каждый из которых является результатом отдельной компиляции (или сборки) и, возможно, даже не написан на одном и том же исходном языке. Каждый объектный файл имеет ряд экспортируемых символов и почти всегда ряд импортированных символов.
Задача компоновщика состоит в том, чтобы создать готовый исполняемый файл, в котором не осталось неопределенных импортированных символов. (Конечно, я лгу, если динамическое связывание разрешено, но потерпите меня.) Для этого он начинает с модулей, названных явно в командной строке ссылки (и, возможно, неявно в его конфигурации) и предполагает, что любой модуль явно названный должен быть частью готового исполняемого файла. Затем он пытается найти определения для всех неопределенных символов.
Обычно именованные объектные модули ожидают получить символы из какой-нибудь библиотеки, например 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().
Как уже упоминалось, существуют компоновщики, способные отделять отдельные функции от модулей и включать только те, которые фактически используются. Во многих случаях это безопасно. Но обычно безопаснее предположить, что ваш компоновщик не делает этого, если у вас нет специальной документации.
Еще кое-что, о чем следует помнить, это то, что большинство компоновщиков делают как можно меньше проходов через файлы и библиотеки, имена которых указаны в командной строке, и создают свою таблицу символов по ходу работы. На практике это означает, что рекомендуется всегда указывать библиотеки после всех объектных модулей в командной строке ссылки.
Это зависит от линкера.
например. Microsoft Visual C++ имеет параметр «Включить связывание на уровне функций», поэтому вы можете включить его вручную.
(Я предполагаю, что у них есть причина не просто включать его все время ... может быть, ссылка медленнее или что-то в этом роде)
Обычно (статические) библиотеки состоят из объектов, созданных из исходных файлов. Что обычно делают компоновщики, так это включают объект, если есть ссылка на функцию, предоставляемую этим объектом. если ваш исходный файл содержит только одну функцию, то только эта функция будет добавлена компоновщиком. Существуют более сложные компоновщики, но большинство компоновщиков на языке C по-прежнему работают так, как описано выше. Доступны инструменты, которые разбивают исходный код C, содержащий несколько функций, на исходные файлы искусственно меньшего размера, чтобы сделать статическое связывание более точным.
Если вы используете разделяемые библиотеки, вы не влияете на размер компиляции, используя большее или меньшее их количество. Однако ваш размер среды выполнения будет включать их.
Эта лекция в Academic Earth дает довольно хороший обзор, о ссылках говорится во второй половине доклада. , IIRC.
Без всякой оптимизации, да, включится. Компоновщик, однако, может быть в состоянии оптимизировать, статически анализируя код и пытаясь удалить недостижимый код.
Это зависит от компоновщика, но, как правило, в окончательный исполняемый файл включаются только те функции, которые действительно вызываются. Компоновщик работает, ища имя функции в библиотеке, а затем используя код, связанный с именем.
Книг по компоновщикам очень мало, что странно, если подумать, насколько они важны. Текст для хорошего можно найти здесь.
Это зависит от параметров, переданных компоновщику, но обычно компоновщик пропускает объектные файлы в библиотеке, на которые нигде нет ссылок.
$ 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
Это зависит от компоновщика и от того, как была собрана библиотека. Обычно библиотеки представляют собой комбинацию объектных файлов (основным исключением являются библиотеки импорта). Старые компоновщики втягивали вещи в образ выходного файла с точностью до объектных файлов, которые были помещены в библиотеку. Таким образом, если бы функция a() и функция b() находились в одном и том же объектном файле, они обе были бы в выходном файле, даже если бы фактически была ссылка только на одну из двух функций.
Это причина, по которой вы часто будете видеть ориентированные на библиотеки проекты с политикой одной функции C для каждого исходного файла. Таким образом, каждая функция упаковывается в свой собственный объектный файл, и компоновщики без проблем загружают только то, на что ссылаются.
Обратите внимание, однако, что более новые компоновщики (конечно, более новые компоновщики Microsoft) имеют возможность извлекать только те части объектных файлов, на которые есть ссылки, поэтому сегодня меньше необходимости применять политику «одна функция на исходный файл», хотя есть разумные аргументы в пользу того, что это нужно сделать в любом случае для ремонтопригодности.