Подтвердить что ты не робот

Статическое связывание C и С++: просто копия?

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

4b9b3361

Ответ 1

  • Вся библиотека? - Нет.
  • Только функции, которые вы назвали? - Нет.
  • Что-то еще? - Да.

Это, конечно, не бросает во всю библиотеку.

Но он не обязательно включает только "функции, используемые в объектных файлах".

Компонент сделает рекурсивно построенный список, какие объектные модули в библиотеке удовлетворяют вашим символам undefined.

Затем он будет включать в себя каждый из этих объектных модулей.

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

Ответ 2

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

Для GCC это выполняется в два этапа:

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

    -fdata-sections -function-sections

  • Свяжите единицы перевода вместе, используя флаг оптимизации компоновщика (это приводит к тому, что компоновщик удаляет разделы без ссылок):

    -Wl, - ГЦ-секции

Итак, если у вас есть один файл с именем test.cpp, в котором были объявлены две функции, но один из них не использовался, вы можете опустить неиспользуемый с помощью следующей команды в gcc (g++):

gcc -Os -fdata-sections -ffunction-sections test.cpp -o test.o -Wl,--gc-sections

(Обратите внимание, что -O - дополнительный флаг компилятора, который сообщает GCC оптимизировать размер)

Что касается MSVC, то связывание уровня функции выполняет одно и то же. Я считаю, что для этого является флаг компилятора (для сортировки в разделах):

/Gy

И затем флаг компоновщика (для удаления неиспользуемых разделов):

/OPT:REF

Ответ 3

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

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

Ответ 4

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

Ответ 5

Он будет использовать только используемые функции и символы (если не указано иное, но это может быть сложно).

Боковой вопрос:

Это может быть проблемой, если вы f.ex. есть некоторые классы, которые просто регистрируются на factory. Никто не вызывает эти классы напрямую, поэтому они не будут включены и, следовательно, не зарегистрированы в factory. Есть способы обойти это (обычно, объявляя некоторую анонимную переменную в файле заголовка, который ссылается на исходный файл).

Ответ 6

Зависит от компоновщика. Некоторые линкеры ленивы и просто бросают всю библиотеку. Другая крайность - это компоновщики, которые вставляют только необходимый код в исполняемый файл.

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

Пример:

puts_test.cpp

#include <cstdio>
using namespace std;

int main(void)
{
  puts("Hello World\n");
  return 0;
}

printf_test.cpp

#include <cstdio>
using namespace std;

int main(void)
{
  printf("%s\n", "Hello World");
  return 0;
}

В приведенном выше примере функция puts не требует дополнительного кода для синтаксического разбора форматирования или преобразования чисел в текст. Это базовая линия, потому что она требует минимальной библиотечной функции.

В примере с использованием printf требуется больше функциональности. Функция printf требует разбора строки форматирования и вывода текста.

Ожидаемый результат: исполняемый файл printf должен быть больше, чем исполняемый файл puts. Большинство компиляторов будут использовать во всем коде для функции printf для разрешения символов (например, для отображения float s), даже если эта часть кода не используется. Более интеллектуальные (и дорогостоящие) компиляторы разбивают функцию printf и включают только те части, которые используются или требуются. В приведенном выше примере компилятор должен включать только части для обработки текста и не включать код для форматирования целых чисел и значений с плавающей запятой.

Леновый компилятор или в режиме отладки скопирует всю библиотеку для примера puts, тем самым делая исполняемые файлы одинакового размера.

Сравнение символов

Платформы * nix и Cygwin предоставляют инструменты для получения символов из исполняемых файлов. Одна такая утилита nm. Запустите nm для каждого исполняемого файла, направляя вывод в текстовый файл. Сравните два текстовых файла. Ленивые компиляторы должны иметь одинаковые символы; за исключением того, что их местоположения могут отличаться (что не важно для проблемы).