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

Posted

技术标签:

【中文标题】带有 -L/usr/local/lib 的巨大可执行文件大小【英文标题】:Massive executable size with -L/usr/local/lib 【发布时间】:2020-05-08 22:27:44 【问题描述】:

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

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

iostream 似乎对我的测试产生了最大的影响。

我通过反复试验确定/usr/local/lib 中的30MB 文件libstdc++.a 是问题的根源。也就是说,我将/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/gcc 中复制,我猜这是有道理的,因为问题文件名为 libstdc++

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

我的问题是:为什么?为什么将带有libstdc++.a 的目录添加到搜索路径会增加二进制大小?据我所知,除非使用-l&lt;library&gt; 明确说明,否则不应从链接器搜索路径链接任何内容。是否与首先搜索 /usr/local/lib 并隐式添加 -lstdc++ 有关,因此可能链接错误的库...?

【问题讨论】:

g++ -v 应该让您更好地了解它在做什么。我的猜测是它将代码从libstdc++.a(目标文件的存档)加载到您的可执行文件中,而不是从 DLL 动态加载。 @KeithThompson 在使用-v 时,/usr/local/lib 似乎出现在所有其他链接器搜索目录之前,这可以解释它更喜欢该目录中的 libstdc++ ......然而我觉得更奇怪的是-shared-libgcc 是否显示为在COLLECT_GCC_OPTIONS 中设置...这不会阻止静态链接 libstdc++ 吗?还是不识别/usr/local/lib中的那个? 【参考方案1】:

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

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

为什么将带有 libstdc++.a 的目录添加到搜索路径会增加二进制大小? 据我所知,除非使用 -l

明确说明,否则不应从链接器搜索路径链接任何内容

您在第二句话中的陈述完全正确。然而,你不是在作曲 或查看整个链接器命令行。 g++ - C++ 的 GNU 前端驱动程序 编译和链接 - 在幕后组成链接器的命令行, 当编译完成并准备好进行链接时。它接受任何 您在自己的命令行中指定的链接选项,将它们转换 如有必要,添加到 the system linker, 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 目录后搜索 - 以及它们的顺序 您需要使用 -verbose 选项运行 ld 本身。你可以这样做 将-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++.alibstdc++.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++ 中的 /usr/local/lib 应该同时放置静态库和共享库 在那里,所以仍然没有代码膨胀。你的问题没有给我任何见解 了解这种情况是如何产生的。

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

您在仅引用 &lt;vector&gt; 的程序中观察到的膨胀要少得多 设施比一个引用&lt;iostream&gt; 设施。那是因为 与&lt;iostream&gt; 相比,&lt;vector&gt; 工具将库代码拉入静态链接的次数要少得多

在评论中你想知道为什么-shared-libgcc 选项的存在 不妨碍与/usr/local/lib/libstdc++.a的联动。这是因为libgcc 不是libstdc++, 而-shared-libgcc只需要libgcc.so的链接

【讨论】:

感谢您的回答;尽管/usr/local/lib 从未出现在-Wl,-verbose 的输出中,但我确实觉得很奇怪...事实上,SEARCH_DIR(x) 只出现了三次。这可能是 Cygwin 的怪癖吗? 嗨。顺便说一句,我也很惊讶,但我不是 Cygwin 专家,我想关注您的系统以评论其工具链的细节。但是,我敢打赌, /usr/local/lib 无论如何都是默认的链接器搜索目录。这是类 unix 操作系统的一个非常基本的约定。

以上是关于带有 -L/usr/local/lib 的巨大可执行文件大小的主要内容,如果未能解决你的问题,请参考以下文章

在 Perl 中读取带有巨大文本节点的 xml 的实用方法

带有巨大 BufferdImage 的小型 pdf 文件结果

目标的可执行性

使用 Pandas 在巨大的 CSV 中解析带有嵌套值的 JSON 列

分别构建第二个可执

Linux可执二进制行文件和库依赖查看方法