Непосредственная (и, возможно, очевидная) причина раздувания программы - когда вы связываете программу раздутым способом - заключается в том, что все символы, на которые она ссылается в стандартной библиотеке 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