静态库和动态库

2021/03/01 Go 编译原理

顾名思义,静态库可以理解为该库相对于可执行目标文件是静态的,而动态库相对于可执行文件是动态的。静态库跟可执行文件捆绑在一起,不需要依赖操作系统中其它库;动态库则一般固定在操作系统的特定位置,如/usr/lib目录中,如果可执行文件依赖于动态库,一旦该库被移动或损坏,则无法运行。有兴趣可以进行以下测试,我使用的是 docker 中的容器,可千万别用自己的系统试。

root@b64869b93f5d:~# mv /usr/lib/ /usr/lib.bak
root@b64869b93f5d:~# mv /usr/lib.bak/ /usr/lib
mv: error while loading shared libraries: libacl.so.1: cannot open shared object file: No such file or directory

第一次mv时一切正常,第二次mv时却提示mv: error while loading shared libraries: libacl.so.1: cannot open shared object file: No such file or directory。就是在加载共享库时发生错误,找不到libacl.so.1这个库。之所以出现这种错误,就是mv命令使用了动态库libacl.so.1,而这个动态库就在/usr/lib中,由于修改了该目录名,就不能通过目录链接到该库。

通过以上测试,可以对动态库的原理进行简单的推断了:使用了动态库的可执行程序,并不需要将该库编译到可执行文件中,只需要知道这些库在磁盘的具体路径即可,在运行时,再通过路径来加载动态库。

静态库跟动态库相反,会直接将库链接到可执行文件中,省略了运行时寻找库这一步骤。

接下来通过两段 cgo 代码看看两者具体的区别。

number 库

创建number/number.h

// number/number.h
int number_add_mod(int a, int b, int mod);

创建number/number.c

// number/number.c
#include "number.h"
int number_add_mod(int a, int b, int mod) {
    return (a+b)%mod;
}

静态库

生成libnumber.a静态库,.aarchived

$ cd number
$ gcc -c -o number.o number.c
$ ar rcs libnumber.a number.o

以下的#cgo命令,分别是编译和链接参数。CFLAGS通过-I ./numbernumber库对应的头文件所在目录加入头文件检索路径。LDFLAGS通过-L ${SRCDIR}/number将编译后的number静态库所在目录加入链接库检索路径,-lnumber表示链接libnumber.a静态库。注意,在链接部分的检索路径不能使用相对路径(C/C++代码的链接程序的限制)。

// static.go
package main
// #cgo CFLAGS: -I./number
// #cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
// #include "number.h"
import "C"
import "fmt"

func main() {
    fmt.Println(C.number_add_mod(10, 5, 12))
}
$ go build static.go
$ ./static
3

动态库

生成libnumber.so动态库,.so是指shared object

$ cd number
$ gcc -shared -o libnum.so number.c

动态库的 Go 语言代码跟静态库的基本一致,为了区分,将动态库改名为num

// dynamic.go
package main
// #cgo CFLAGS: -I./number
// #cgo LDFLAGS: -L${SRCDIR}/number -lnum
//
// #include "number.h"
import "C"
import "fmt"

func main() {
    fmt.Println(C.number_add_mod(10, 5, 12))
}

以上 cgo 配置用于动态库时无法生效,只好使用系统的动态库配置来加载。

运行时链接会通过系统默认的动态库配置来加载库。在 Linux 下,动态库链接路径可以通过/etc/ld.so.conf以及/etc/ld.so.conf.d指定。例如,现在我们的代码在/root目录中,则可以如下配置:

$ echo '/root/number' > /etc/ld.so.conf.d/root.conf
$ ldconfig

ldconfig使配置生效,生成缓存。

现在可以编译代码了。

$ go build dynamic.go
$ ./dynamic
3

当然,除了修改配置之外,还可以直接将动态库移动到/usr/lib或者/usr/lib64等默认加载路径中。

Search

    Table of Contents