基本修养实战篇(八) 动态链前篇之静态库的一些可用优化手段
静态链接的一个显著的问题就是极大的浪费内存空间。
谈到这个概念,我们先回顾一下之前在静态链接那个章节中学到的一些链接器的优化手段:
一个是函数级别链接。也就是说开了-ffunction-sections 和 -fdata-sections , 这俩选项可以使我们的链接器自己去解析依赖,只使用目标文件/静态库中被引用的符号。否则按照编译器的默认行为,有时,我们只使用了静态库仅有的几个功能,但是系统默认会自动把整个静态库全部链接到可执行程序中,造成可执行程序的大小大大增加。
GCC链接操作是以section作为最小的处理单元,只要一个section中的某个符号被引用,该section就会被加入到可执行程序中去。因此,GCC在编译时可以使用 -ffunction-sections和 -fdata-sections 将每个函数或符号创建为一个sections,其中每个sections名与function或data名保持一致。而在链接阶段, -Wl,–gc-sections 指示链接器去掉不用的section(其中-wl, 表示后面的参数 -gc-sections 传递给链接器),这样就能减少最终的可执行程序的大小了。
我们常常使用下面的配置启用这个功能:
CFLAGS += -ffunction-sections -fdata-sections LDFLAGS += -Wl,--gc-sections
还有一个手段是重复代码消除,比如模板和内联函数等,可能会在不同的cpp文件中实例化成相同的函数,这样势必导致重复代码的问题。重复代码容易导致以下问题:空间浪费,地址较容易出错,指令运行效率较低。因为缓存的存在,如果同一份指令有多个不同的副本,那么就会导致cache命中率下降。一个有效的做法是,将每个模板的实例代码都放在一个段内,比如.temp.add<int> 和 .temp.add<float>的段,别的编译实例化的时候也会生成这样的名字,这样链接器在最终链接的时候就可以区分这些模板实例段,然后进行合并同类项。
这里我抛出一个问题来思考,如果有一个程序main.c, 分别引用了libfunca.a 和 libfuncb.a的符号,然后这俩静态库又都依赖libcommon.o的某个符号比如print,那么请问,在main.c的目标文件中,会不会有重复的libcommon.o的符号print呢。或者说,最终的elf的符号表是个什么样子的呢. 一起做个试验看一下吧。
// libCommon.c int libCommonFunc(void) { return 42; }
int libCommonFunc2(void) { return 42; }
int libCommonFunc3(void) { return 42; }
// a.c int funcA() { return libCommonFunc(); } //b.c int funB() { return libCommonFunc(); } //main.c extern int funcA(void); extern int funcB(void); int main() { funcA(); funcB(); return 0; }
首先,gcc -c libCommon.c -o libCommon.o
gcc -c a.c -o a.o
然后 ar -rcs libfunA.a a.o libCommon.o
这里的几个参数我说一下含义:
r: 表示如果.a文件存在就更新它
c: 表示不显示反馈信息
s: 在.a文件开头建立索引
同理:gcc -c b.c -o b.o
ar -rcs libfunB.a b.o libCommon.o
最后,让main.c 链接两个.a文件
gcc main.c libfunA.a libfunB.a -o main
我们使用readelf -a main 查看其符号表时,只看到了一个libCommonFunc。其实readelf -a libfunA.a 就可以很清楚的发现一个问题了:
那就是一个libfunA.a静态文件,其实是libCommonFunc.o 和 a.o 两个目标文件的打包。
在a.o 中libCommonFunc 压根是个UND符号,也就是静态库并不会执行重定位的操作。
只有在生成可执行文件时,这个时候才会去重定位。不过,如果libCommon.c中定义了多个函数,而我们又没有用到的话
他们的确是会在可执行文件中占据空间的:
我们尝试使用上面说的函数链接选项重新编译链接,看看是否能够消除不用的符号
makefile如下:
libCommon.o: libCommon.c gcc -ffunction-sections -fdata-sections -c libCommon.c -o libCommon.o a.o: a.c gcc -c a.c -o a.o b.o: b.c gcc -c b.c -o b.o libfunA.a: a.o libCommon.o ar -rcs libfunA.a a.o libCommon.o libfunB.a: b.o libCommon.o ar -rcs libfunB.a b.o libCommon.o main: main.c libfunA.a libfunB.a gcc main.c libfunA.a libfunB.a -Wl,--gc-sections -o main clean: rm -f main.c.* *.o *.a
再用readelf查看,果然没了,只剩下用到libCommonFunc.