首页 > 解决方案 > 带有 -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++g++cygwinfilesize

解决方案


程序膨胀的直接(也许是显而易见的)原因 - 当您以膨胀方式链接程序时 - 是它在标准 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 链接、gnatADA 链接等。

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++.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++这种情况是不正常的,因为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


推荐阅读