巨大的可执行文件大小 -L/usr/local/lib

Massive executable size with -L/usr/local/lib

我今天遇到了一个与 (cygwin) g++ 生成的二进制大小有关的奇怪问题。

编译使用标准库函数的 C++ 程序并将 -L/usr/local/lib 作为选项传递时,二进制大小绝对是巨大的(12MB 巨大)。

iostream 似乎对我测试过的影响最大。

我通过反复试验确定了 /usr/local/lib 中的 30MB 文件 libstdc++.a 是问题的根源。也就是说,我将 /usr/local/lib 的内容复制到一个单独的目录中,将那个目录添加到 link 路径而不是 /usr/local/lib,然后删除文件,直到二进制大小恢复正常。

试验:

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

control 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

control 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/gcc 中复制,我想这是有道理的,因为问题文件被命名为 libstdc++.

如果您需要更多试用,请告诉我。

我的问题是:为什么? 为什么在搜索路径中添加带有 libstdc++.a 的目录会增加二进制大小所以?据我所知,除非用 -l<library> 明确说明,否则不应从 linker 搜索路径中编辑任何内容。它是否与首先搜索 /usr/local/lib,然后隐式添加 -lstdc++,因此可能 link 错误的库有关...?

程序膨胀的直接(也许是显而易见的)原因 - 当您 link 程序在 膨胀的方式 - 是它在标准中引用的所有符号 C++ 库静态绑定到在 存档 /usr/local/lib/libstdc++.a。所有归档的目标文件 包含那些由 linker 提取并物理合并到 输出程序。

当你link正常方式而不是膨胀方式的程序时,相同 符号动态绑定到 DSO libstdc++.so 提供的定义 linker 位于其搜索目录之一而不是 /usr/local/lib/。在这种情况下,没有目标代码被合并到程序中。 linker 只是注释 这样 运行time loader 在启动时会加载 libstdc++.so 进入同一进程并用 运行time 修补动态引用 地址。

Why does adding a directory with libstdc++.a to the search path increase the binary size so? To my knowledge, nothing should be linked from the linker search path unless stated explicitly with -l

你在第二句中的陈述完全正确。但是,您没有作曲 或者查看整个 linker 命令行。 g++ - C++ 的 GNU 前端驱动程序 编译和 linkage - 正在编写 linker 的命令行,在幕后, 当编译完成并准备好执行 linkage 时。它接受任何东西 link您在自己的命令行中指定的年龄选项,将它们转换 如有必要,将它们添加到 the system linker, ld 理解的等效选项中 到一个新的命令行,然后附加到它的很多样板 linker 选项是不变的 对于 C++ 程序,最后将这个新命令行传递给 ld 以执行 linkage。 (这在某种程度上简化了,但本质上会发生什么)

如果您必须在命令提示符下 ld 自己调用 link C++ 程序,您 每次都必须自己输入所有样板文件,而且永远无法 记住这一切。如果您想查看所有内容,请添加 -v (= verbose) 选项 你调用 g++。其他 GCC 前端对其他语言执行相同的功能:gcc for C linkages; gfortran 对于 Fortran linkages,gnat 对于 ADA linkages,等等

g++ 默认情况下添加到 linkage 选项的所有样板文件中,您将 找到这样的:-

...
-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 系统。所以你看,如果 link 一个带有 g++ 的程序,那么你 -lstdc++ 传递给 默认情况下 linker。如果你没有通过它,那么任何外部引用 在您的代码中无法解析标准 C++ 库的符号,并且 linkage 将因未定义的引用而失败。

下一个问题是 linker 如何将 -lstdc++ 解析为物理 搜索路径中某处的静态或共享库并使用它。

默认情况下是这样的。库选项 -lname 指示 linker 进行搜索, 首先在指定的 -Ldir 目录中,按照它们的命令行顺序,然后 在其默认搜索目录中,按照配置的顺序, files libname.a(静态库)或 libname.so(动态库)。如果 当它找到其中一个时,它会停止搜索并将该文件输入到 link年龄。如果它在同一个搜索目录中找到它们,那么它会选择 libname.so。如果它输入一个共享库然后它执行动态符号绑定 使用库,它不会向程序添加任何目标文件。如果它 输入一个静态库然后它执行静态符号绑定,确实添加对象 文件到程序。

如果您想知道 linker 的默认搜索目录 - 它在哪里 将在耗尽 -L 目录后进行搜索 - 以及它们的顺序 您需要 运行 ld 本身和 -verbose 选项。你可以这样做 将 -Wl,-verbose 传递给前端。

-verbose linker 输出的开始附近,您会发现类似的内容:

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");

这是linker的内置目录搜索顺序。注意它包含 /usr/local/lib。所以你永远不需要指定 -L/usr/local/lib (或任何这些目录)在前端命令行中,对于 g++ 或任何 其他前端,除非您想更改目录搜索顺序。

-Ldir选项在linker命令行中出现的位置 -lname 选项无关紧要。所有 -Ldir 选项适用于所有 -lname 个选项。但是 -Ldir 选项出现的顺序 彼此 很重要,-lname 选项也是如此。

如果您link您的程序没有不必要的link年龄选项:

g++ -Wall test.cpp

link 读者将搜索满足 -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++.alibstdc++.so,它会选择 libstdc++.so。 动态符号绑定完成。没有代码膨胀。

但是如果你link你的程序像:

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

/usr/local/lib/libstdc++.a 存在并且 /usr/local/lib/libstdc++.so 没有,则先查找/usr/local/lib/libstdc++.a一个人被发现 那里是静态 linked。有代码膨胀。

这种情况是不正常的,因为常规和熟练的安装 libstd++/usr/local/lib 应该同时放置静态库和共享库 在那里,所以仍然不会有代码膨胀。你的问题让我没有洞察力 了解这种情况是如何发生的。

当你删除/usr/local/lib/libstdc++.a你发现程序大小 恢复正常。那是因为在没有那个文件的情况下,第一个 库满足 -lstdc++ linker 找到的又是它的常态 libstdc++.so.

您在一个仅引用 <vector> 的程序中观察到更少的膨胀 设施比一个引用 <iostream> 设施。那是因为 与 <iostream>

相比,<vector> 设施将更少的库代码拉入静态 linkage

在评论中你想知道为什么 -shared-libgcc 选项的存在 不会阻止 link/usr/local/lib/libstdc++.a 的年龄。这是因为 libgcc 不是 libstdc++, -shared-libgcc 只需要 link 年龄 libgcc.so