当C库静态链接时,整个库会添加到可执行文件中吗?

时间:2018-06-29 16:29:07

标签: c linker static-libraries static-linking

背景信息:我正在尝试比较执行一些数值计算的两段代码的内存需求。为此,我正在将编译的C代码的大小与静态链接的数学库进行比较。

但是,我发现一些奇怪的结果,似乎表明正在添加整个库。我在下面描述一个MWE

// Program ex1.c
# include<math.h>
void main (void)
{
    float a = exp(2);

}

// Program ex2.c
# include <math.h>
void main(void)
{
    float a = exp(2);
    float b = pow(3,4);
    float c = sin(3.14159);
}

我将文件编译如下:

gcc -static -o ex1static.out ex1.c -lm
gcc -static -o ex2static.out ex2.c -lm

如果程序1的已编译对象仅包含exp()的代码,而程序2的已编译对象包含exp(),pow()和sin()的代码,则第二个对象将大于首先。但是两个对象的大小相同,为912.6 kB。

为什么会发生这种情况,有什么方法可以确保仅将所需的代码部分添加到对象中?

1 个答案:

答案 0 :(得分:3)

静态库是目标文件的归档,并且在静态库中进行链接只会添加归档中那些解析至少一个未定义引用的目标文件成员。

为确保仅添加所需的代码,静态库需要由小的目标文件组成,最好每个目标文件中都包含一个导出的全局文件。

除此之外,如果使用-ffunction-sections / -fdata-sections编译库,然后将--gc-sections传递给链接器,则可以实现类似的效果。

-ffunction-sections -fdata-sections方法基本上等效于每源一个全局方法,但是使用源文件来建立边界更加灵活,因为有时可能希望将它们组合在一起(较大的翻译单元可能会导致更多紧凑且经过优化的代码。

无论如何,就您而言(库不受您控制),您只能尝试-Wl,--gc-sections(gcc的-Wl选项为gcc应该传递给链接器的前缀) 通过您的示例和glibc,我能够从原始的849KiB减少大约41KiB。

不是很令人印象深刻,但是glibc并不是在考虑静态链接的情况下构建的。 使用musl-libc之类的libc库可以获得更好的结果。

for ex in ex{1,2}.c; do for flg in '' -Wl,--gc-sections; do echo "$ex $flg"; musl-gcc -O0 $ex -static -lm $flg call.c && \ls -l a.out ; done ; done
ex1.c 
-rwxrwx--- 1 pjmp pjmp 8064 Jun 29 19:11 a.out
ex1.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7744 Jun 29 19:11 a.out
ex2.c 
-rwxrwx--- 1 pjmp pjmp 8064 Jun 29 19:11 a.out
ex2.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7744 Jun 29 19:11 a.out

现在这更好,但是您可能想知道为什么示例1和2的大小相同。

如果添加-Wl,--print-map,则会发现musl-libc的相关目标文件根本没有被包括在内 在任一情况下。原因是,gcc知道这些标准函数,并且它通过插入操作码而不是生成的函数调用来作弊。通过添加由另一个翻译单元促进的间接层,您可以在某种程度上克服gcc的作弊行为。

call.c:

double call1(double(*X)(double A), double A) { return X(A); }
double call2(double(*X)(double A,double B), double A, double B){ return X(A,B); }

Ex1.c

# include<math.h>
double call1(double(*X)(double A), double A);
double call2(double(*X)(double A,double B), double A, double B);
int main (void)
{
    float a = call1(exp,2);
}

Ex2.c

# include <math.h>
double call1(double(*X)(double A), double A);
double call2(double(*X)(double A,double B), double A, double B);
int main(void)
{
    float a = call1(exp,(2));
    float b = call2(pow,3,4);
    float c = call1(sin,(3.14159));
}

现在,这给了我

Ex1.c 
-rwxrwx--- 1 pjmp pjmp 8216 Jun 29 19:15 a.out
Ex1.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 7984 Jun 29 19:15 a.out
Ex2.c 
-rwxrwx--- 1 pjmp pjmp 17088 Jun 29 19:15 a.out
Ex2.c -Wl,--gc-sections
-rwxrwx--- 1 pjmp pjmp 16856 Jun 29 19:15 a.out

-两个示例之间的显着差异,这可能是由于musl是如何组成的 many small source/object files,以便在静态链接时不会添加(或不多于)相关参考代码。