盘古开天辟地。我们写了个程序,想要给终端输出一些内容,不可避免地我们需要使用系统库,在我们写程序的过程中我们经常会碰到需要使用库的过程,无论是系统库还是第三方库,我们统称为 lib
库。
而库的链接分为两种,分别有静态库和动态库。
1. 静态库
静态库可以看作一堆的目标文件的集合,可能包含了很多函数的实现。在 linux
最常用的 C 语言静态库 libc
位于 /usr/lib/libc.a
,它由成百上千个 C 语言程序,比如输入输出有printf.o,scanf.o
,文件操作有fread.o
,fwrite.o
等等。把这些零散的文件提供给使用者会造成不便,于是通常人们用 ar
压缩程序将这些目标文件压缩到一起,然后对这些文件进行编号和索引,最后形成了 libc.a
这个文件。
运行命令查看其中包含的内容,然后过滤查看fread
$ ar -t /usr/lib/x86_64-linux-gnu/libc.a | grep fread
iofread.o
__freading.o
__freadable.o
iofread_u.o
fread_chk.o
fread_u_chk.o
如果我们要在其中寻找 printf.o
文件可以使用命令
$ objdump -t /usr/lib/x86_64-linux-gnu/libc.a | grep printf.o
vfprintf.o:文件格式 elf64-x86-64
vprintf.o:文件格式 elf64-x86-64
reg-printf.o:文件格式 elf64-x86-64
fprintf.o:文件格式 elf64-x86-64
printf.o:文件格式 elf64-x86-64
snprintf.o:文件格式 elf64-x86-64
sprintf.o:文件格式 elf64-x86-64
asprintf.o:文件格式 elf64-x86-64
dprintf.o:文件格式 elf64-x86-64
vfwprintf.o:文件格式 elf64-x86-64
fxprintf.o:文件格式 elf64-x86-64
iovsprintf.o:文件格式 elf64-x86-64
fwprintf.o:文件格式 elf64-x86-64
swprintf.o:文件格式 elf64-x86-64
vwprintf.o:文件格式 elf64-x86-64
wprintf.o:文件格式 elf64-x86-64
vswprintf.o:文件格式 elf64-x86-64
vasprintf.o:文件格式 elf64-x86-64
iovdprintf.o:文件格式 elf64-x86-64
vsnprintf.o:文件格式 elf64-x86-64
obprintf.o:文件格式 elf64-x86-64
可以看到 printf.o
文件就在其中。
假如以我们写的 helloworld.c
程序为例
#include <stdio.h>
int main(int argc, char *argv[])
{printf("Hello,World!\n");
return 0;
}
默认情况下直接运行
$ gcc helloworld.c -o helloworld
会进行动态链接,我们查看生成的文件大小
$ ls -l helloworld
-rwxr-xr-x 1 gnc gnc 8304 10 月 11 15:06 helloworld
可以看到大小为 8303bytes
,我们使用选项-static
来使 gcc
进行静态链接:
$ gcc -static helloworld.c -o helloworld
查看大小
$ ls -l helloworld
-rwxr-xr-x 1 gnc gnc 844704 10 月 11 15:09 helloworld
可以看到大小显著变大。
2. 动态库
静态链接好处就是链接完成以后,运行程序不需要原来的支持库,但缺点是文件大小较大,并且更新困难。
所以出现了动态链接,也就是不要静态地进行链接,而是等到动态运行时再进行链接。动态链接的符号定位是发生在运行时首先进行地址空间分配,然后再来运行程序。
具体的链接过程也比较复杂,我们只需要知道如何来创建这种动态库即可。
3. gcc 创建静态和动态库
源文件add.c
/* add.h */
void setSummand(int summand);
int add(int summand);
/* add.c */
#include <stdio.h>
int gSummand;
void setSummand(int summand) {gSummand = summand;}
int add(int summand) {return gSummand + summand;}
void __attribute__ ((constructor)) initLibrary(void) {
//
// Function that is called when the library is loaded
//
printf("Library is initialized\n");
gSummand = 0;
}
void __attribute__ ((destructor)) cleanUpLibrary(void) {
//
// Function that is called when the library is »closed«.
//
printf("Library is exited\n");
}
源文件answer.c
/* answer.h */
int answer();
/* answer.c */
#include "add.h"
int answer() {setSummand(20);
return add(22); // Will return 42 (=20+22)
}
源文件main.c
#include <stdio.h>
#include "add.h"
#include "answer.h"
int main(int argc, char* argv[]) {setSummand(5);
printf("5 + 7 = %d\n", add(7));
printf("And the answer is: %d\n", answer());
return 0;
}
然后我们创建目录 .bin/static
和./bin/shared
。
完成以后的目录结构如下
$ tree
.
├── add.c
├── add.h
├── answer.c
├── answer.h
├── bin
│ ├── shared
│ └── static
└── main.c
3 directories, 5 files
我们运行命令生成目标文件
$ gcc -c main.c -o bin/main.o
# 静态库
$ gcc -c add.c -o bin/static/add.o
$ gcc -c answer.c -o bin/static/answer.o
# 动态库
gcc -c -fPIC add.c -o bin/shared/add.o
gcc -c -fPIC answer.c -o bin/shared/answer.o
3.1 创建静态库
运行命令
$ ar rcs bin/static/libtq84.a bin/static/add.o bin/static/answer.o
将两个目标文件打包压缩为一个静态库文件。
静态链接
$ gcc bin/main.o -Lbin/static -ltq84 -o bin/static-main
运行
$ ./bin/static-main
Library is initialized
5 + 7 = 12
And the answer is: 42
Library is exited
3.2 创建动态库
运行命令
$ gcc -shared bin/shared/add.o bin/shared/answer.o -o bin/shared/libtq84.so
然后进行动态链接
$ gcc bin/main.o -Lbin/shared -ltq84 -o bin/use-shared-library
运行
$ ./bin/use-shared-library
./bin/use-shared-library: error while loading shared libraries: libtq84.so: cannot open shared object file: No such file or directory
会提示找不到动态链接库,我们需要修改 LD_LIBRARY_PATH
变量来指向我们的动态库路径
export LD_LIBRARY_PATH=$(pwd)/bin/shared
然后再运行
./bin/use-shared-library
Library is initialized
5 + 7 = 12
And the answer is: 42
Library is exited
就可以了,当然你也可以选择将该动态库放置到路径 /usr/lib
下,运行下面的命令
sudo mv bin/shared/libtq84.so /usr/lib
sudo chmod 755 /usr/lib/libtq84.so