【总结】Linux C++ 链接库与链接

Posted on Posted in 计算机3,013 views

        C++工程中避免不了要编写和使用库,由于不熟悉,每次在写makefile时都在链接库上花费很多时间,利用空余时间整体梳理一下Linux 下C++链接库相关的知识。

零、介绍

0.1、linux ELF

        ELF(Executable and Linkable Format)即可执行连接文件格式,是Linux默认的目标文件格式。分为3钟类型:

    • 可重定位文件:主要是编译过程中产生的.o/.a文件,可以和其他目标文件一起创建可执行文件或者共享目标文件;

    • 可执行文件:用于生成进程映像,载入内存执行,常见的诸如编译好的可执行文件a.out;

    • 共享目标文件:用于和其他共享目标文件或者可重定位文件一起生成elf目标文件或者和执行文件一起创建进程映像,例如so库文件;

        我们可以通过filesizereadelfobjdump命令查看可执行文件等情况:

        1476257067332900.png

        1476257104186210.png

0.2、编译过程

        程序从源码到可执行文件,需要经过如下的编译过程:

        1476257264115198.png

        在g++中对应的命令如下:

    • g++ -E 预处理,不生成文件,需要把它重定向到一个输出文件里;

    • g++ -S 预处理和编译,把文件编译成为汇编代码,生成.s的汇编代码;

    • g++ -c 预处理、编译和汇编,生成.o的obj文件;

    • g++ -o 制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,注意不是链接;

0.3、链接种类

        1、全静态: 链接.a

        2、全动态: 链接.so

        3、半静态 (libgcc,libstdc++)

0.4、常用G++命令

        1、-Idir:指定搜索头文件的路径

        2、-ixxx:指定头文件;

        3、-Ldir:指定搜索库的路径;

        4、-lxxx:指定链接库文件;

        5、-g:只是编译器,在编译的时候,产生调试信息;

        6、-static:此选项将禁止使用动态库;之后的库会静态链接;

        7、-shared(-G):此选项将尽量使用动态库,为默认选项;

        8、-Wall:一般使用该选项,允许发出GCC能够提供的所有有用的警告;

        9、-w:关闭所有警告,建议不要使用此项;

        10、-fpic:编译器就生成位置无关目标码.适用于共享库;

        11、-fPIC:编译器就输出位置无关目标码.适用于动态连接,即使分支需要大范围转移;

        12、-Dmacro:相当于C语言中的#define macro   

        13、-Dmacro=defn:相当于C语言中的#define macro=defn

        14、-Umacro:相当于C语言中的#undef macro   

        15、-undef:取消对任何非标准宏的定义

        16、-Wl.option:传递option给连接程序;若option中间有逗号,就将option分成多个选项,然后传递给会连接程序.

一、静态链接库

1.1、介绍

        在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件。

        静态库特点总结:

    • 静态库对函数库的链接是放在编译时期完成的。

    • 程序在运行时与函数库再无瓜葛,移植方便,不会发生应用程序在不同Linux版本下的标准库不兼容问题。

    • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件, 生成的文件比较大。

    • 静态库适宜于较小的程序。

1.2、编译

        1、关于命名规则:

                静态链接库libxxx.a,其中xxx为静态链接库的名称

        2、Linux下使用ar工具、Windows下vs使用lib.exe,将目标文件压缩到一起,并且对其进行编号和索引,以便于查找和检索。一般创建静态库的步骤如图所示:

        1476259170316827.png

        3、关于ar命令简单介绍如下:

            ar rcs  libxxx.a xx1.o xx2.o

                参数r:在库中插入模块(替换),当插入的模块名已经在库中存在,则替换同名的模块。

                参数c:创建一个库,不管库是否存在,都将创建。

                参数s:创建目标文件索引,这在创建较大的库时能加快时间。

            ar t libxxx.a 显示库文件中有哪些目标文件,只显示名称。

            ar tv libxxx.a 显示库文件中有哪些目标文件,显示文件名、时间、大小等详细信息。

        4、本文章对应的Makfile中对应的是:

OBJ:=la_a.o
       #la_b.o
TARGET:=libla.a
 
all: $(OBJ)
       ar -rs $(TARGET) $^
 
clean:
       @-rm -rf $(TARGET) $(OBJ)
 
%.o: %.cc
       g++ -c $< -o $@

1.3、链接使用

        对于一个程序或者其他动态库,我们可以在其链接时引入需要静态链接库,在链接时链接库可以指定链接库或者全名libxxxx.a,如下所示:

OBJ:=main_a.o
 
all: $(OBJ)
       g++ -o main_a main_a.o -static -L../lib_a -lla 
       #g++ -o main_a main_a.o -static ../lib_a/libla.a
 
clean:
       @-rm -rf $(OBJ) main_a main_so main_stdc
 
main_a.o: main_a.cc
       g++ -c -I../lib_a $< -o $@

二、动态库

2.1、介绍

        简单对比一下静态库和动态库的加载如下:

        1476259417422282.png1476259452585900.png

        动态库特点总结:

    • 动态库把对一些库函数的链接载入推迟到程序运行的时期。

    • 可以实现进程之间的资源共享。(因此动态库也称为共享库)

    • 将一些程序升级变得简单。

    • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。

        Window与Linux执行文件格式不同,在创建动态库的时候有一些差异。

    • 在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。

    • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。

2.2、编译

        1、命名规则:

        动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号,minor是副版本号。版本号也可以没有。

        2、本文实例:

OBJ:=lso_a.o 
       #lso_b.o
TARGET:=liblso.so.1.0
TARLIB:=liblso.so
 
all: $(OBJ)
       g++ -shared -o $(TARGET) $^
       ln -s $(TARGET) $(TARLIB)
 
clean:                 
       @-rm -rf $(TARGET) $(OBJ) $(TARLIB)
 
%.o: %.cc
       g++ -fPIC -c $< -o $@

2.3、链接使用

1、对于一个程序或者其他动态库,我们可以在其链接时引入需要静态链接库,在链接时链接库可以指定链接库或者全名libxxxx.a,如下所示:

OBJ:=main_so.o                                     
           
all: $(OBJ) 
       g++ -o main_so main_so.o -L../lib_so -llso -Wl,-rpath,/mnt/hgfs/毕业/x_1610_linux_cpp_link/exe
       # g++ -o main_so main_so.o /mnt/hgfs/毕业/x_1610_linux_cpp_link/exe/liblso.so #不推荐
 
clean:
       @-rm -rf $(OBJ) main_a main_so main_stdc
 
main_so.o: main_so.cc                            
       g++ -fPIC -c -I../lib_so $< -o $@

        关于-shared说明:不可以加,否则编译出来的程序不可用,why?

        2、动态库的显式调用 – 未验证

        在Linux下显式调用动态库

            #include <dlfcn.h>,提供了下面几个接口:

                A、void * dlopen( const char * pathname, int mode ):函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。

                B、void* dlsym(void* handle,const char* symbol):dlsym根据动态链接库操作句柄(pHandle)与符号(symbol),返回符号对应的地址。使用这个函数不但可以获取函数地址,也可以获取变量地址。

                C、int dlclose (void *handle):dlclose用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。

                D、const char *dlerror(void):当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。

            在Windows下显式调用动态库,应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序必须:

                A、调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。

                B、调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。

                C、使用完 DLL 后调用 FreeLibrary。

2.4、其他补充

        1、ldd a.out #查看需要的动态链接库

        1476260312833035.png

        2、动态链接库搜索目录

                A. 环境变量 LD_LIBRARY_PATH

                B. 从/etc/ld.so.conf找(每次修改需要ldconfig刷新缓存)

                C. 默认的系统路径, 先是/lib(/lib64)然后是/usr/lib (/usr/lib64)

                D. 还可以编译时指定搜索路径:-Wl,-rpath,dir1:dir2:…:dirN (多个冒号间隔)

        3、库版本管理

    虽然动态链接库方便,但也容易出现版本问题,所以动态链接库一般都带有版本号,为了使连接更方便,一般都会建立个没有版本号的软连接文件链接到全名的库文件。

ln -s liblso.so.1.0 liblso.so

三、半静态

3.1、介绍

        半静态链接方式则有针对性的将 libgcc 和 libstdc++ 两个标准库非动态链接。

        从实际应用当中发现,最理想的标准库链接方式就是半静态链接,通常会选择将 libgcc 与 libstdc++ 这两个标准库静态链接,从而避免应用程序在不同 Linux 版本间标准库依赖不兼容的问题发生。

        半静态的特点如下:

    • 形式:-static-libgcc -L. -pthread -lrt -ldl

    • 灵活度大,能够针对不同的标准库采取不同的链接策略,从而避免不兼容问题发生。

    • 结合了全静态与全动态两种链接方式的优点。

    • 比较难识别哪些库容易发生不兼容问题,目前只有依靠经验积累。

    • 某些功能会因选择的标准库版本而丧失。

3.2、编译

本文实例:

OBJ:=lstdc_a.o 
# lstdc_b.o
TARGET:=liblstdc.so.1.0
TARLIB:=liblstdc.so   
      
all: $(OBJ)
       g++ -static-libgcc -static-libstdc++ -shared -o $(TARGET) $^
       ln -s $(TARGET) $(TARLIB)
       
clean:
       @-rm -rf $(TARGET) $(OBJ) $(TARLIB)
       
%.o: %.cc 
       g++ -fPIC -c $< -o $@

3.3、链接使用

本文实例:

OBJ:=main_stdc.o

all: $(OBJ)         
       g++ -o main_stdc main_stdc.o -L../lib_stdc -llstdc -Wl,-rpath,/mnt/hgfs/毕业/x_1610_linux_cpp_link/exe 
       #-static-libgcc -static-libstdc++
       
clean:
       @-rm -rf $(OBJ) main_a main_so main_stdc
       
main_stdc.o: main_stdc.cc
       g++ -fPIC -c -I../lib_stdc $< -o $@

        标注:-static-libgcc -static-libstdc++ 编译main_stdc时加上-static-libgcc -static-libstdc++这大小为main_so: 43K,liblso.so:1M。不加main_so: 14k,liblso.so:1M。此时main_so没有静态链接libgcc、libstdc++

四、总结

4.1、库生成方式

        1476260698307573.png

4.2、链接方式总结

        1476260730929157.png

4.3、结果对比

        1、libla.a

            size:3K

        1476260892673361.png

        2、liblso.so.1.0

            size:9K

        1476260914532544.png

        3、liblstdc.so.1.0

            size:1M

        1476260940239682.png

        4、main_a

            不加static,size:15K

        1476260964241075.png

            加static,main:1.6M

        1476260986543530.png

        5、main_so

            size:15K

        1476261053817422.png

        6、main_stdc

            不加-static-libgcc -static-libstdc++,size:14K

        1476261078877389.png

            加-static-libgcc -static-libstdc++,size:43K

        1476261102216951.png

五、参考

        本文源代码:https://git.oschina.net/evan-xia/x_1610_linux_cpp_link

        1、ELF:http://www.iloveandroid.net/2015/11/17/studyElf/

        2、g++参数介绍:http://www.cnblogs.com/lidan/archive/2011/05/25/2239517.html

        3、C++静态库与动态库:http://www.cnblogs.com/skynet/p/3372855.html

        4、ar命令详解:http://blog.csdn.net/hxg130435477/article/details/8217247

        5、VS关于链接库:https://blog.evanxia.com/2016/02/238

        6、半静态:http://blog.jobbole.com/103668/


转载标明出处:https://blog.evanxia.com/2016/10/1127