基本概念
程序编译一般需要经预 处理、编译、汇编和链接 几个步骤。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库有两种:静态库(.a、.lib)和动态库(.so、.dll)
- 库文件:程序在实际使用中,有些需要重复使用的公共代码,通常被编译为“库文件”。
- 静态链接库:连接器从库文件取得代码,复制到生成的可执行文件中。(可执行文件中包含库代码的完整拷贝,多次使用会有多份冗余拷贝)
- 动态链接库(Dynamic Link Library):程序运行时候调用库函数才被载入,库独立于现有程序,但包含程序需要调用的一些函数。
静态库及动态库的不同点:
二者的不同点在于代码被载入的时刻不同。
-
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
-
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
Linux 静态库
Linux创建静态库
Linux静态库命名规范,必须是lib[your_library_name].a
:lib为前缀,中间是静态库名,扩展名为.a。
-
首先,将代码文件编译成目标文件.o(StaticMath.o)
$ g++ -c StaticMath.cpp
注意带参数-c,否则直接编译为可执行文件
-
然后,通过ar工具将目标文件打包成.a静态库文件
$ ar -crv libstaticmath.a StaticMath.o
生成静态库 libstaticmath.a
。大一点的项目会编写 makefile 文件(CMake 等等工程管理工具)来生成静态库。
Linux 下使用静态库
Linux下使用静态库,只需要在编译的时候,指定静态库的搜索路径(-L选项)、指定静态库名(不需要lib前缀和.a后缀,-l选项)。
$ g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
-L
:表示要连接的库所在目录-l
:指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库的名称。
Linux 动态库
动态链接库的名字形式为libxxx.so
,前缀是lib
,后缀名为.so
。
-
针对于实际库文件,每个共享库都有个特殊的名字“soname”。在程序启动后,程序通过这个名字来告诉动态加载器该载入哪个共享库。
-
在文件系统中,soname仅是一个链接到实际动态库的链接。对于动态库而言,每个库实际上都有另一个名字给编译器来用。它是一个指向实际库镜像文件的链接文件
lib+soname+.so
。
Linux 下创建动态库
- 首先,生成目标文件,此时要加编译器选项
-fpic
g++ -fPIC -c DynamicMath.cpp
-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
- 然后,生成动态库,此时要加链接器选项
-shared
g++ -shared -o libdynmath.so DynamicMath.o
-shared指定生成动态链接库。
- 上面两个步骤也可以合并为一个命令:
g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp
Linux 下使用动态库
执行的时候是如何定位共享库文件的呢?
-
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
-
对于elf格式的可执行程序,是由
ld-linux.so
来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache
文件列表—/lib/
,/usr/lib
目录找到库文件后将其载入内存。
如何让系统能够找到它:
-
如果安装在
/lib
或者/usr/lib
下,那么ld
默认能够找到,无需其他操作。 -
如果安装在其他目录,需要将其添加到
/etc/ld.so.cache
文件中,步骤如下:编辑
/etc/ld.so.conf
文件,加入库文件所在目录的路径运行
ldconfig
,该命令会重建/etc/ld.so.cache
文件
调用库的常用问题:
调用动态库的时候有几个问题会经常碰到:有时,明明已经将库的头文件所在目录 通过 -I
include进来了,库所在文件通过 -L
参数引导,并指定了-l
的库名,但通过ldd
命令察看时,就是死活找不到你指定链接的so
文件,这时你要作的就是通过修改 LD_LIBRARY_PATH
或者/etc/ld.so.conf
文件来指定动态库的目录。
通常这样做就可以解决库无法链接的问题了。
附: ldd
命令可以查看一个可执行程序依赖的共享库,例如
可见 ln命令依赖于libc库和ld-linux库
$ ldd /bin/lnlibc.so.6
=> /lib/libc.so.6 (0×40021000)/lib/ld-linux.so.2
=> /lib/ld- linux.so.2 (0×40000000)
给小迷糊的进一步补充!
库使用范例
使用如下命令进行编译 $gcc -c testlib.c -o testlib.o
用如下命令连接: $gcc testlib.o -lhello -o testlib
连接时要注意,假设libhello.o
和libhello.a
都在缺省的库搜索路径下/usr/lib
下,如果在其它位置要加上-L
参数 与与静态库连接麻烦一些,主要是参数问题。还是上面的例子:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello
注:这个特别的-WI,-Bstatic
参数,实际上是传给了连接器ld
。指示它与静态库连接,如果系统中只有静态库当然就不需要这个参数了。 如果要和多个库相连接,而每个库的连接方式不一样,比如上面的程序既要和libhello
进行静态连接,又要和libbye
进行动态连接,其命令应为:
$gcc testlib.o -o testlib -WI,-Bstatic -lhello -WI,-Bdynamic -lbye
动态库的路径问题
为了让执行程序顺利找到动态库,有三种方法:
(1)把库拷贝到/usr/lib
和/lib
目录下。
(2)在LD_LIBRARY_PATH
环境变量中加上库所在路径。
例如动态库 libhello.so
在/home/ting/lib
目录下,以bash
为例,使用命令:
$export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/ting/lib
(3) 修改/etc/ld.so.conf
文件,把库所在的路径加到文件末尾,并执行ldconfig
刷新。这样,加入的目录下的所有库文件都可见。
gcc/ g++ 的常用编译参数
-
-L
:表示要链接的库所在的目录。-L. 表示要链接的库在当前目录, -L/usr/lib 表示要连接的库在/usr/lib下。目录在/usr/lib时,系统会自动搜索这个目录,可以不用指明。 -
-l
(L的小写):指定链接时需要的动态库,注意不是库文件名称,比如库文件为 libtest.so,那么库名称为test。在给出的名字前面加上lib,后面加上.a/.so来确定库的名称 -
-include
:包含头文件,这个很少用,因为一般情况下在源码中,都有指定头文件。 -
-I
(i 的大写):指定头文件的所在的目录,可以使用相对路径。 -
-shared
:指定生成动态链接库 -
-fPIC
:表示编译为位置独立的代码,用于编译共享库。不用此选项的话编译后的代码是位置相关的所以动态载入时事通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码共享的目的。 -
-static
:指定生成静态链接库。 -
-g
:编译器在编译的时候产生调试信息。 -
-c
:只激活预处理、编译和汇编,也就是把程序做成目标文件(.o文件) 。 -
-Wl,options
:把参数(options)传递给链接器ld 。如果options 中间有逗号,就将options分成多个选项,然后传递给链接程序。
————————————————
gcc 编译生成的文件
首先gcc编译的时候会有不同种的文件生成,从先到后:
- 源文件:一般你编写的.c文件
- 预编译文件:gcc把源文件中的宏定义等展开放到预编译文件中,用.i文件来表示
- 汇编文件:gcc把.i文件进一步编译生成汇编文件,用.s文件来表示,一般自己写的汇编也是.s表示的
- 目标文件汇编器把.s文件生成.o文件
- 可执行文件:链接器把.o文件链入静态库或者别的.o文件,生成可执行文件
gcc命令中-o参数的说明
-o
参数表示的是 outfile 的意思,跟在该参数后面的是你想给予的编译后的文件的名称
通过-o
参数来描述gcc中其他的参数,我把他们分成三种:
-
不需使用
-o
参数: 如果想要生成.o
文件,就不需要加入-o
参数,因为编译器会自动生成一个.o
文件.。 e.g. 你有一个hello.c
文件,通过gcc -c hello.c
就可以生成一个hello.o
文件 -
可用可不用: 想要生成
.s
汇编文件或者是最后的可执行文件,可以不加-o
参数,因为编译器会生成一个a.out
文件,但有歧义。有一个hello.c
文件,通过gcc hello.c
生成了可执行文件a.out
, 通过gcc -s hello.c
也会生成一个a.out
汇编文件 -
一定要用:如果只是进行预编译,就一定要加
-o
参数,不然gcc -E hello.c
会打屏,没有任何文件生成.
————————————————
参考文档: