顾名思义,静态库可以理解为该库相对于可执行目标文件是静态的,而动态库相对于可执行文件是动态的。静态库跟可执行文件捆绑在一起,不需要依赖操作系统中其它库;动态库则一般固定在操作系统的特定位置,如/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
静态库,.a
指archived
。
$ cd number
$ gcc -c -o number.o number.c
$ ar rcs libnumber.a number.o
以下的#cgo
命令,分别是编译和链接参数。CFLAGS
通过-I ./number
将number
库对应的头文件所在目录加入头文件检索路径。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
等默认加载路径中。