c++ - 带有 -L/usr/local/lib 的巨大可执行文件大小
问题描述
我今天遇到了一个与 (cygwin) g++ 生成的二进制大小有关的奇怪问题。
-L/usr/local/lib
在编译使用标准库函数并作为选项传递的 C++ 程序时,二进制大小绝对是巨大的(12MB 巨大)。
iostream
似乎对我测试过的影响最大。
通过反复试验,我已将 30MB 文件确定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
无法在带有 gcc 的 C 中复制,我想这是有道理的,因为问题文件名为libstdc++。
如果您需要更多试验,请告诉我。
我的问题是:为什么?为什么将目录添加libstdc++.a
到搜索路径会增加二进制大小?据我所知,除非明确说明,否则不应从链接器搜索路径链接任何内容-l<library>
。它是否与/usr/local/lib
首先被搜索-lstdc++
并被隐式添加有关,因此可能链接错误的库......?
解决方案
程序膨胀的直接(也许是显而易见的)原因 - 当您以膨胀方式链接程序时 - 是它在标准 C++ 库中引用的所有符号都静态绑定到存档中找到的定义/usr/local/lib/libstdc++.a
。所有包含这些定义的归档目标文件都由链接器提取并物理合并到输出程序中。
当您以正常方式而不是膨胀方式链接程序时,相同的符号会动态绑定到 DSO 提供的定义libstdc++.so
,链接器位于其搜索目录之一中,而不是
/usr/local/lib/
. 在这种情况下,没有目标代码被合并到程序中。链接器只是对其进行注释,以便运行时加载程序在启动它时将其加载libstdc++.so
到同一进程中,并使用它们的运行时地址修补动态引用。
为什么将带有 libstdc++.a 的目录添加到搜索路径会增加二进制大小?据我所知,除非使用 -l 明确说明,否则不应从链接器搜索路径链接任何内容
你在第二句话中的陈述是完全正确的。但是,您没有编写或查看整个链接器命令行。g++
- 用于 C++ 编译和链接的 GNU 前端驱动程序 - 正在后台编写链接器的命令行,当编译完成并准备好进行链接时。它接受您在自己的命令行中指定的任何链接选项,如有必要,将它们转换为系统链接器ld
理解的等效选项,将它们添加到新的命令行,然后向其附加许多对于 C++ 不变的样板链接器选项程序,最后通过这个新的命令行ld
来执行链接。(这有点简化,但本质上会发生什么)
如果您必须在命令提示符下调用ld
自己来链接 C++ 程序,那么您每次都必须自己键入所有样板文件,并且永远无法记住所有内容。如果您想查看所有内容,请-v
在调用时添加 (=verbose) 选项g++
。其他 GCC 前端对其他语言执行相同的功能:gcc
对于 C 链接;gfortran
Fortran 链接、gnat
ADA 链接等。
在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++
到其搜索路径中某处的物理静态或共享库并使用它。
默认情况下,它是这样的。库选项-lname
指示链接器首先按照-Ldir
命令行顺序在指定目录中搜索,然后按照配置顺序在其默认搜索目录中搜索文件libname.a
(静态库)或libname.so
(动态库)。如果并且当它找到其中任何一个时,它会停止搜索并将该文件输入到链接中。如果它在同一个搜索目录中找到它们,那么它会选择
libname.so
. 如果它输入一个共享库,那么它将与该库执行动态符号绑定,这不会将任何目标文件添加到程序中。如果它输入一个静态库,那么它会执行静态符号绑定,这会将目标文件添加到程序中。
如果您想知道链接器的默认搜索目录 - 用尽-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
,否则您无需
在前端命令行、forg++
或任何其他前端指定(或任何这些目录)。
-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
。动态符号绑定完成。没有代码膨胀。
但是如果你像这样链接你的程序:
g++ -Wall test.cpp -L/usr/local/lib`
当/usr/local/lib/libstdc++.a
存在与/usr/local/lib/libstdc++.so
不存在时,则/usr/local/lib/
先搜索;libstdc++.a
单独在那里找到并且是静态链接的。有代码膨胀。
libstd++
这种情况是不正常的,因为into的常规和熟练安装/usr/local/lib
应该将静态库和共享库都放在那里,所以仍然不会出现代码膨胀。你的问题让我无法了解这种情况是如何出现的。
当您删除时,/usr/local/lib/libstdc++.a
您发现程序大小恢复正常。那是因为在没有该文件的情况下,第一个满足-lstdc++
链接器发现的库又是它通常的
libstdc++.so
.
您观察到在仅<vector>
引用设施的程序中的膨胀比在引用设施的程序中要少得多<iostream>
。这是因为这些<vector>
设施将更少的库代码放入静态链接中<iostream>
在评论中,您想知道为什么该-shared-libgcc
选项的存在不会阻止与/usr/local/lib/libstdc++.a
. 这是因为libgcc
不是libstdc++
,-shared-libgcc
只需要链接libgcc.so
推荐阅读
- scheme - 球拍博士定义了学生语言中的错误。定义:函数体只需要一个表达式,但发现了 3 个额外的部分
- android - Lottie Animation Android 的帧大小问题
- tensorflow - Tensorflow 步数/秒逐渐下降
- java - 如何使用java为Excel(xls)文件设置密码
- r - 如何更改 geom_line 的路径?
- c# - 无法使用 Regex.Match 获取文本
- python - 通过 Web Scraping Python 检索 Imgur 图像链接
- knockout.js - 如何根据使用 knockout.js 中的数据绑定的条件将值绑定到 colspan?
- matrix - `matrix * vector`、`matrix' * vector` 和 `copy(matrix') * vector` 之间的非直观性能差异
- c# - WPF 控件库面向 .NET Core 3 还是 .NET Framework?