上一篇谈到了头文件,链接

img

c程序的编译过程:

  • 预处理(Pre-Processing)
  • 编译(Compiling)
  • 汇编(Assembling)
  • 链接(Linking)

库是写好的现有的,成熟的,可以复用的代码。也有叫包的,有官方sdk提供的标准库,也有一些中间件为了推广各种语言开发者使用中间件,提供了api,称为client库。不同于其他语言的库,如go的第三方库,就是纯代码。而c语言的,是一种可执行代码的二进制形式,可以被操作系统载入内存执行。库分两种:静态库、动态库:

其中windows下

  • 动态库:dll
  • 静态库: .lib

linux下

  • 动态库:.so
  • 静态库:.a

mac下

  • 动态库:.dylib
  • 静态库:.a

1. 静态库与静态链接

静态链接:在链接阶段,会将汇编生成的目标文件 .o/.obj引用到的库一起链接打包到可执行文件中。

静态库:很多目标文件经过压缩打包后形成的一个文件

优点

  • 静态库对函数库的链接是放在编译时期完成的
  • 程序在运行时与函数库再无瓜葛,移植方便

缺点

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

命名规则

lib[libname].a

以zeromq为例,ar工具将目标文件打包成.a静态库文件

image-20211018013950876

2. 动态库与动态链接

由于静态库的缺点,会造成空间上的浪费,更新、部署、发布也会带来麻烦,因为静态库对函数库的链接是放在编译时期完成的,所以如果静态库有所变动,就只能程序整个重新编译,发布。

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。
  • 可以实现进程之间的资源共享。(因此动态库也称为共享库)
  • 将一些程序升级变得简单。
  • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显式调用)。

与创建静态库不同的是,不需要打包工具(ar、lib.exe),直接使用编译器即可创建动态库。

3. 编译拆解

img

我们以zeromq的client代码为例,分步拆解编译流程。这里使用GCC, 通过它能够更好地控制整个编译过程。

3.1 预处理阶段

gcc -E  -I /opt/homebrew/Cellar/zeromq/4.3.4/include client.c -o client.i

-E 选项,可以让编译器在预处理后停止编译过程,并输出预处理结果。

这一阶段主要对包含的头文件(#include )和宏定义(#define, #ifdef … )进行处理。生成 *.i 文件。

3.2 编译阶段

gcc -S client.i -o client.s

-S 选项,表示在程序编译期间,在生成汇编代码后,停止, -o 输出汇编代码文件。

这一阶段检查代码的规范性,是否有语法错误等。以确定代码实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。用户可以使用-S 选项进行查看,该选项只进行编译而不进行汇编,生成汇编代码。

3.3 汇编阶段

gcc -c client.s -o client.o

使用 gcc 直接从源代码生成目标代码。

除了上面使用gcc外,也可以直接用汇编器,从汇编代码生成目标代码:

as hello.s -o hello.o

以上3步,是可以一步到位:

gcc -c -I /opt/homebrew/Cellar/zeromq/4.3.4/include client.c

3.4 链接

# 动态链接 链接动态库
gcc client.o /opt/homebrew/Cellar/zeromq/4.3.4/lib/libzmq.dylib -o client

# 静态链接 链接静态库
gcc client.o -L /opt/homebrew/Cellar/zeromq/4.3.4/lib -lzmq  -o clientStatic

Linux 下的库文件在命名时有一个约定,那就是应该以lib 三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用 -l 选项指定链接的库文件名时可以省去lib 三个字母,也就是说GCC 在对 -lzmq 进行处理时,会自动去链接名为 libzmq.a

Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾, mac下以.dylib结尾)和静态链接库(通常以.a 结尾),两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的 。默认情况下,GCC 在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上**-static** 选项,强制使用静态链接库。

3.5 总结

通过以上编译步骤的拆解,当在其他编译器报错的时候,大概也能猜测问题与解决问题的方法。如找不到头文件,找不到库等等。