发布于 

GCC: 静态库

概要

如果你对 GCC 的编译 C语言的流程不清楚, 建议在阅读本文之前先去看一下 GCC: 编译C语言的流程 这篇文章, 篇幅短小精悍, 阅读后, 至少可以扫扫盲😜.

本篇博文用到的一些基础知识点:

1.GCC

gcc -c 选项含义:

1
Only run preprocess, compile, and assemble steps

-c 选项只是进行了预处理, 编译, 汇编的阶段, 不会进行链接的操作.

2.静态库

Linux 上的静态库,本质是一些目标文件的归档文件.

3.静态库和共享库区别

[1].使用共享库可以节省内存.

比如 libc,系统中几乎所有的进程都映射 libc 到自己的进程地址空间,而 libc 的只读部分在物理内存中只需要存在一份,就可以被所有进程共享,这就是“共享库”这个名称的由来了.

[2].使用共享库可以很方便地升级库文件而不需要重新编译应用程序.

[3].共享库占用更少的体积.

在运行时做动态链接.而在链接静态库时, 链接器会把静态库中的目标文件取出来和可执行文件真正链接在一起.

创建步骤

创建静态库的步骤如下:

[1]. 写源文件.
[2]. 通过 gcc -c x.c 生成目标文件.
[2]. 归档. 用 ar 归档目标文件,生成静态库.
[3]. 写头文件, 便于使用者知道怎么使用该静态库.

使用静态库时,在源码中包含对应的头文件,链接时记得链接自己的库.

下面结合具体例子, 展开讲.

写源文件

目录结构
1

示例文件都是很简单的代码, 附录可以查看完整示例.

生成目标文件

将 libs 目录下面的(c)源文件进行预处理, 编译和汇编.
注意这里没有进行链接.

1
gcc -c libs/person.c libs/eat.c libs/play.c libs/sleep.c

执行城后, 会生成对应的 .o 文件.

归档

libperson.a 是要生成的库文件.

库文件都以 lib 开头, 静态库以. a 为后缀. 所以一般是 lib+ 名字.a

1
ar rs libperson.a person.o sleep.o play.o eat.o

ar: 类似于 tar, 用来对文件进行库打包.

r 选项: 将其后面的文件列表添加到文件包(libperson.a)中, 如果 libperson.a 不存在就创建它, 如果 libperson.a 已经存在且里面有同名的目标文件就进行替换操作.

s 选项: 为静态库创建索引.这个索引会被链接器使用.

ranlib 命令也可以为静态库创建索引. 所以上面的命令可以等效为下面的两个命令.

1
2
ar r libperson.a person.o sleep.o play.o eat.o
ranlib libperson.a

写头文件

写一个 person.h 文件, 便于调用者查看库如何使用.

person.h

1
2
3
4
5
6
7
#ifndef _PERSON_H
#define _PERSON_H
extern void init(int pUid);
extern void eat();
extern void play();
extern void sleep();
#endif

使用静态库

1
gcc main.c -L. -lperson -Ilibs -o main

-L 选项: 告诉编译器去哪里找库文件, 这里的 _L. 表示在当前目录.
如果不用 -L 选项, 即使库文件在当前目录, 编译器也不会去找, 所以该选项不能少.

报错信息如下:

1
2
ld: library not found for -lperson
clang: error: linker command failed with exit code 1 (use -v to see invocation)

-lperson: 告诉编译器要链接 libperson.a 库.

-I: 告诉编译器到哪里找头文件.
如果不指定头文件的查找目录, 也会报错:

1
2
3
4
main.c:2:10: fatal error: 'person.h' file not found
#include "person.h"
^~~~~~~~~~
1 error generated.

此时的目录结构:
1

链接成功后, 可以执行生成的 main (可执行)文件.

1
./main
1
2
3
uid: 101 eating
uid: 101 playing
uid: 101 has sleep

有趣的实验

猜想一下如果有两个库一个是共享库, 一个是静态库, 而且二者除了后缀不一样, 名字都一样如 libperson.a 和 libperson.so, 那么调用方如何来选择对应的库文件呢?

我们把 GCC: 共享库 里面生成的共享库 libperson.so 放到当前的目录, 重新编译链接 main.c 文件.

1
gcc main.c -L. -lperson -Ilibs -o main

再次执行 ./main, 得到结果是这样的:

1
2
3
From sharedlib.uid: 101 eating
From sharedlib.uid: 101 playing
From sharedlib.uid: 101 has sleep

以上的结果, 说明链接器会优先选择共享库其次才是静态库.

Linux(MacOS 也一样) 的 GCC 默认链接动态库,只有当动态库不存在时,才去链接静态库.
若是需要强制指定静态库需要指定选项 -static.但是在 MacOS 上面不支持该选项.

1
gcc -static main.c -L. -lperson -Ilibs -o main

附录

示例完整代码

main.c

1
2
3
4
5
6
7
8
#include <stdio.h>
#include "person.h"
int main() {
init(101);
eat();
play();
sleep();
}

person.h

1
2
3
4
5
6
7
#ifndef _PERSON_H
#define _PERSON_H
extern void init(int pUid);
extern void eat();
extern void play();
extern void sleep();
#endif

person.c

1
2
3
4
int uid;
void init(int pUid) {
uid = pUid;
}

eat.c

1
2
3
4
5
#include <stdio.h>
extern int uid;
void eat() {
printf("uid: %i eating\n", uid);
}

play.c

1
2
3
4
5
#include <stdio.h>
extern int uid;
void play() {
printf("uid: %i playing\n", uid);
}

sleep.c

1
2
3
4
5
#include <stdio.h>
extern int uid;
void sleep() {
printf("uid: %i has sleep\n", uid);
}

GCC 系列博文

GCC: 编译 C 语言的流程

GCC: Homebrew 安装 GCC 和 Binutils

GCC: 共享库

GCC: 静态库


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

veryitman