Огромный размер исполняемого файла с -L / usr / local / lib - PullRequest
2 голосов
/ 09 мая 2020

Сегодня я столкнулся с причудливой проблемой, связанной с двоичными размерами, создаваемыми (cygwin) g ++.

При компиляции программы C ++, которая использует стандартные библиотечные функции и передает -L/usr/local/lib в качестве опции , размер двоичного файла абсолютно огромен (12 МБ).

iostream, похоже, имеет наибольший эффект из того, что я тестировал.

Я обнаружил файл размером 30 МБ libstdc++.a в /usr/local/lib как источник проблемы методом проб и ошибок. То есть я скопировал содержимое /usr/local/lib в отдельный каталог, добавил его в путь ссылки вместо /usr/local/lib и удалил файлы до тех пор, пока двоичный размер не упал до нормального.

Испытания :

KEY:
(<group>)
`<command>` -> <size of resulting binary in bytes>

элемент управления 1 (буквально ничего) [без эффекта] :

int main() {}
(control)
`g++ -Wall test.cpp` -> 159,574
`g++ -Wall -Os test.cpp` -> 159,610
`g++ -Wall -Os -s test.cpp` -> 8,704
(test)
`g++ -Wall test.cpp -L/usr/local/lib` -> 159,574
`g++ -Wall -Os test.cpp -L/usr/local/lib` -> 159,610
`g++ -Wall -Os -s test.cpp -L/usr/local/lib` -> 8,704

элемент управления 2 (динамическое использование какой-либо другой библиотеки - например, stb_image ...) [без эффекта] :

#include "stb_image.h"
int main() {
    int width, height, channels;
    unsigned char *data = stbi_load("image.jpg", &width, &height, &channels, 0);
}
(control)
`g++ -Wall test.cpp stb_image.so` -> 159,944
`g++ -Wall -Os test.cpp stb_image.so` -> 159,980
`g++ -Wall -Os -s test.cpp stb_image.so` -> 8,704
(test)
`g++ -Wall test.cpp -L/usr/local/lib stb_image.so` -> 159,944
`g++ -Wall -Os test.cpp -L/usr/local/lib stb_image.so` -> 159,980
`g++ -Wall -Os -s test.cpp -L/usr/local/lib stb_image.so` -> 8,704

вектор (шаблоны) [небольшой эффект] :

#include <vector>
int main() {
    std::vector<int> v;
    v.push_back(2);
}
(control)
`g++ -Wall test.cpp` -> 190,228
`g++ -Wall -Os test.cpp` -> 160,429
`g++ -Wall -Os -s test.cpp` -> 8,704
(test)
`g++ -Wall test.cpp -L/usr/local/lib` -> 1,985,106
`g++ -Wall -Os test.cpp -L/usr/local/lib` -> 906,760
`g++ -Wall -Os -s test.cpp -L/usr/local/lib` -> 72,192

iostream [основной эффект] :

#include <iostream>
int main() {
    std::cout << "iostream" << std::endl;
}
(control)
`g++ -Wall test.cpp` -> 161,829
`g++ -Wall -Os test.cpp` -> 161,393
`g++ -Wall -Os -s test.cpp` -> 8,704
(test)
`g++ -Wall test.cpp -L/usr/local/lib` -> 11,899,614
`g++ -Wall -Os test.cpp -L/usr/local/lib` -> 11,899,344
`g++ -Wall -Os -s test.cpp -L/usr/local/lib` -> 828,416

Невозможно реплицировать в C w / g cc, что, я думаю, имеет смысл учитывая, что проблемный файл называется libstdc ++ .

Если вам нужны дополнительные испытания, дайте мне знать.

Мой вопрос: Почему? Почему добавление каталога с libstdc++.a в путь поиска так увеличивает двоичный размер? Насколько мне известно, из пути поиска компоновщика ничего не должно быть связано, если явно не указано -l<library>. Связано ли это с тем, что сначала выполняется поиск /usr/local/lib, а -lstdc++ добавляется неявно, поэтому, возможно, связана неправильная библиотека ...?

1 Ответ

0 голосов
/ 09 мая 2020

Непосредственная (и, возможно, очевидная) причина раздувания программы - когда вы связываете программу раздутым способом - заключается в том, что все символы, на которые она ссылается в стандартной библиотеке C ++, статически привязаны к определениям, найденным в архиве. /usr/local/lib/libstdc++.a. Все заархивированные объектные файлы, содержащие эти определения, извлекаются компоновщиком и физически объединяются в программу вывода.

Когда вы связываете программу обычным, а не раздутым способом, одни и те же символы динамически привязываются к определения, предоставленные DSO libstdc++.so, которые компоновщик размещает в одном из своих каталогов поиска, отличных от /usr/local/lib/. В этом случае в программу не добавляется объектный код. Компоновщик просто аннотирует его, так что загрузчик времени выполнения при его запуске загрузит этот libstdc++.so в тот же процесс и исправит ссылки Dynami c с их адресами времени выполнения.

Почему добавляется каталог с libstdc ++. а к пути поиска увеличить двоичный размер так? Насколько мне известно, ничто не должно быть связано с путем поиска компоновщика, если явно не указано -l

Ваше утверждение во втором предложении строго верно. Однако вы не составляете и не видите всю командную строку компоновщика. g++ - интерфейсный драйвер GNU для компиляции и компоновки C ++ - за кулисами составляет командную строку компоновщика, когда компиляция завершена и он готов к компоновке. Он принимает любые параметры связывания, которые вы указали в своей собственной командной строке, при необходимости преобразует их в эквивалентные параметры, понятные системному компоновщику, ld, добавляет их в новую командную строку, а затем добавляет к ней много шаблонных параметров компоновщика, которые инвариантны для программ на C ++, и, наконец, передает эту новую командную строку в ld для выполнения связывания. (Это несколько упрощает, но по сути то, что происходит)

Если бы вам пришлось вызывать ld самостоятельно, чтобы связать программу на C ++ в командной строке, вам пришлось бы каждый раз вводить все шаблоны самостоятельно и никогда не уметь все это запомнить. Если вы хотите увидеть все это, добавьте параметр -v (= подробный) при вызове g++. Другие интерфейсы G CC выполняют ту же функцию для других языков: gcc для C связей; gfortran для связей Fortran, gnat для связей ADA и т. Д. c.

Среди всех шаблонов, которые g++ по умолчанию добавляет к параметрам привязки, вы найдете примерно следующее: -

...
-L/usr/lib/gcc/x86_64-linux-gnu/9 \
-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu \
-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib \
-L/lib/x86_64-linux-gnu \
-L/lib/../lib \
-L/usr/lib/x86_64-linux-gnu \
-L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/9/../../.. \
...
-lstdc++
...

Это из моей собственной системы Ubuntu 19.10. Итак, вы видите, что если связать программу с g++, тогда будет передать -lstdc++ компоновщику по умолчанию. Если вы его не передали, то любые внешние ссылки, сделанные в вашем коде на символы стандартной библиотеки C ++, не могут быть разрешены, и связывание не будет выполнено для неопределенных ссылок.

Следующий вопрос - как решает компоновщик -lstdc++ в физический c или разделяемую библиотеку где-нибудь на пути поиска и использует его.

По умолчанию это так. Параметр библиотеки -lname указывает компоновщику выполнять поиск сначала в указанных каталогах -Ldir в порядке их командной строки, а затем в каталогах поиска по умолчанию в их настроенном порядке для любого из файлов libname.a (статус c библиотека) или libname.so (динамическая c библиотека). Если и когда он находит один из них, он прекращает поиск и вводит этот файл в привязку. Если он находит их обоих в одном каталоге поиска, он выбирает libname.so. Если он вводит разделяемую библиотеку, то он выполняет динамическую c привязку символа к библиотеке, которая не добавляет никаких объектных файлов в программу. Если он вводит библиотеку stati c, тогда он выполняет привязку символов stati c, которая делает добавляет объектные файлы в программу.

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

Рядом с началом вывода компоновщика -verbose вы найдете что-то вроде:

SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu"); \
SEARCH_DIR("=/lib/x86_64-linux-gnu"); \
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu"); \
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64"); \
SEARCH_DIR("=/usr/local/lib64"); \
SEARCH_DIR("=/lib64"); \
SEARCH_DIR("=/usr/lib64"); \
SEARCH_DIR("=/usr/local/lib"); \
SEARCH_DIR("=/lib"); \
SEARCH_DIR("=/usr/lib"); \
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64"); \
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");

Это компоновщик встроенный порядок поиска в справочнике. Обратите внимание, что он содержит /usr/local/lib. Таким образом, вам никогда не нужно указывать -L/usr/local/lib (или любой из этих каталогов) в командной строке внешнего интерфейса, для g++ или любого другого внешнего интерфейса , если вы не хотите изменить порядок поиска в каталоге.

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

Если вы линкуете свою программу без лишних опций связывания:

g++ -Wall test.cpp

, компоновщик будет искать физическую библиотеку, удовлетворяющую -lstdc++.

В моей системе , первый каталог, который он будет искать, - /usr/lib/gcc/x86_64-linux-gnu/9, и там он найдет:

$ ls /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.*
/usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.a  /usr/lib/gcc/x86_64-linux-gnu/9/libstdc++.so

Таким образом, у него есть выбор из libstdc++.a и libstdc++.so, и он выбирает libstdc++.so. Привязка символов Dynami c выполнена. Нет раздувания кода.

Но если вы свяжете свою программу, например:

g++ -Wall test.cpp -L/usr/local/lib`

, когда /usr/local/lib/libstdc++.a существует, а /usr/local/lib/libstdc++.so нет, тогда сначала выполняется поиск /usr/local/lib/; Только libstdc++.a находится там и статически связан. Раздутый код.

Такая ситуация ненормальна, потому что обычная и профессиональная установка libstd++ в /usr/local/lib должна разместить там как stati c, так и разделяемые библиотеки, так что кода все равно не будет. раздувание. Ваш вопрос не дает мне представления о том, как могла возникнуть такая ситуация.

Когда вы удалили /usr/local/lib/libstdc++.a, вы обнаружили, что размер программы вернулся к нормальному. Это потому, что в отсутствие этого файла первая библиотека, удовлетворяющая -lstdc++, которую нашел компоновщик, снова была его обычной libstdc++.so.

Вы заметили гораздо меньше раздувания в программе, ссылающейся только на <vector> объектов, чем в одном, ссылаясь на объекты <iostream>. Это потому, что средства <vector> втягивают гораздо меньше библиотечного кода в привязку c, чем <iostream>

. В комментарии вы задаетесь вопросом, почему наличие параметра -shared-libgcc не препятствует связыванию с /usr/local/lib/libstdc++.a. Это потому, что libgcc не libstdc++, а -shared-libgcc просто требует привязки libgcc.so

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