你的室友不会写作业,你把代码变成了一包压缩饼干
前置:为什么 Linux 不准给目录建硬链接
先玩一个实验。
bash
[mhb@bite-alicloud lesson29]$ mkdir dir
[mhb@bite-alicloud lesson29]$ ln dir dir-hard
ln: 'dir': hard link not allowed for directory
系统直接拒了。 软链接给目录建没问题------ln -s dir dir-soft 随便建。硬链接不行。
但诡异的事来了。你进到 dir 里面,看一下:
bash
[mhb@bite-alicloud dir]$ ls -lid dir
1967156 drwxrwxr-x 2 mhb 4096 Feb 7 14:42 dir
[mhb@bite-alicloud dir]$ ls -lia
total 8
1967156 drwxrwxr-x 2 mhb 4096 Feb 7 14:42 .
inode 是 1967156。再看 dir 本身------inode 也是 1967156。
. 和 dir 共享同一个 inode。 换个问法:. 就是给目录 dir 建的硬链接。
这不就是"只许州官放火,不许百姓点灯"?系统自己可以用 . 和 .. 给目录建硬链接,但不准你建。
为什么
一句话:系统分不清你建的硬链接到底是个真目录,还是指向目录的硬链接。
假设系统允许你给 /etc 建一个硬链接叫 /tmp/evil。这两个名字指向同一个 inode,inode 的类型是目录。哪天 find / 遍历目录树------遍历到 /tmp,进了 evil,发现它也是个目录(因为 inode 类型就是目录),继续往里走......走着走着又回到 /etc,而 /etc 又指向同一个 inode。环形路径。 find 就死循环了。
软链接为什么没事?因为软链接是独立文件,它的文件类型是 l (link),不是 d(directory)。find 看到类型 l 就知道"这是个链接,不递归进去"。系统能甄别。
那 . 和 .. 为什么又没事?因为它们是特殊命名的文件 ------操作系统、用户、工具开发者之间有一项共识:在任何目录下,. 就是当前目录,.. 就是上一级目录。所有遍历工具都会自动跳过它们。你在写代码读取目录时,不也自动跳过 . 和 .. 了吗?
所以这个规则的本质,是在功能完备(需要绝对路径和相对路径切换)和系统安全(防止环形遍历)之间找到了一个平衡点。 平衡点就是------系统自己克制地用 . 和 ..,不准用户乱建。
还有一个附带结论:新建一个目录,它的硬链接数默认是 2。为什么是 2?因为有两组文件名→inode 映射:
- 目录名本身 → inode
- 目录内部的
.→ 同一个 inode
如果你在这个目录里再建子目录,硬链接数还会增加------因为子目录里的 .. 也指向这个 inode。
文件系统就此收官
从磁盘物理结构(CHS → LBA → 块设备),到分区、分组、超级块、inode、数据块,到路径解析、dentry 缓存、挂载,再到软硬链接------文件系统这一整个话题,到这里全部画上句号。
下面,切换到下一个话题:库的制作与使用。
你是个老实人。因为老师布置了大作业,让你用系统调用封装一套文件操作函数和字符串操作函数,你吭哧吭哧写完了。
c
// my_stdio.h ------ 文件操作封装
#pragma onceA
#include <stdio.h>
FILE *my_fopen(const char *path, const char *mode);
void my_fclose(FILE *fp);
c
// my_stdio.c ------ 实现
#include "my_stdio.h"
// ... 具体实现,此处省略一堆 write/read/open/close 系统调用封装
c
// my_string.h ------ 字符串操作封装
#pragma once
size_t my_string_len(const char *s);
c
// my_string.c ------ 实现
#include "my_string.h"
size_t my_string_len(const char *s) {
const char *end = s;
while (*end) end++;
return end - s;
}
你甚至还写了个 main.c 测了一把:
c
// main.c
#include "my_stdio.h"
#include "my_string.h"
int main() {
FILE *fp = my_fopen("log.txt", "w");
const char *message = "hello world";
int len = my_string_len(message);
printf("len: %d\n", len);
my_fclose(fp);
return 0;
}
编译,链接,运行。log.txt 出来了,长度求出来了。你很满意。
然后你的室友来了。
"兄弟,老师那个封装作业我不会写,把你代码拷我一份呗。"
你不能给源代码
不是小气。是你室友这货------他自己不写,拿你的代码直接交。万一老师查重,你们俩代码一模一样,他学号还排你前面,老师先查他再查你,最后判定是你抄他。
你说:不行,源代码不能给。
但你又不是那种见死不救的人。你开始想:到底什么才是"把我的功能给你用"这道题的最小解?
点 o 文件就够了
回忆一下,你的 C 程序是怎么变成可执行程序的:
源文件 .c → 预处理 → 编译 → 汇编 → 目标文件 .o → 链接 → 可执行程序
关键在这里:所有 .c 文件最终都得变成 .o 文件,然后链接在一起。
即便你把源代码给室友,他也得把他的 main.c 编成 main.o,把你的 my_stdio.c 和 my_string.c 编成 .o,再把三个 .o 链接成可执行程序。
那你干嘛非给他 .c?你直接把你的 .c 提前编成 .o 不就完了?
bash
gcc -c my_stdio.c # → my_stdio.o
gcc -c my_string.c # → my_string.o
.o 文件已经是二进制了,打开全是乱码。老师看不出来,室友也看不出来。
然后你把 .o 和 .h 一起给室友。"头文件里有函数声明,你看头文件就知道怎么调。具体实现在 .o 里,你看不了,但链接的时候能用。"
室友拿了头文件,自己写个 main.c,编成 main.o,然后:
bash
gcc -o myexe main.o my_stdio.o my_string.o
编译通过,运行正常。老师检查------"行,你过吧。"
室友通关了。你的源代码没暴露。
一句话:把别人需要的功能代码编译成 .o,别人就不用看到你的源码了。
就这么回事。
但 .o 文件太多怎么办
后来老师布置的作业越来越多。文件操作、字符串、数学、排序、链表、二叉树......你的 .o 文件从两个变成了两百个。
你室友又来了。你把两百个 .o 拷给他。第二天他跑来:"兄弟少拷了一个,我编不过!"
两个人对了一下午才找到漏了哪个。你心想:能打个包吗?
能。
为什么库这个东西必须存在
一句话:提高开发者效率。
你用的 printf,不用自己手动搓一个。你搓过------要封装文件描述符、封装缓冲区、调底层系统调用、确定刷新策略......这些东西别人写好了,你直接调。
你用的链表、队列、排序、字符串操作,STL 里全有。这些基础的、公共的、被所有程序员广泛使用的基本功能,如果没打包成库,每个程序员都得自己重写一遍。计算机技术的发展会慢成什么样,不用我多说------因为没有巨人肩膀可站。
还有一层:写库的人专业度特别高。 底层库都是很职业的程序员写的,代码质量好、稳定。你们学 C++ 的时候,STL 源码有同学可能都看过------因为人家写得好。用这种稳定的库开发,你写的代码再错都有个下限。
命名 ≠ 理解。知道"库"这个字,和真的搞清楚它怎么来的、怎么给的、怎么用的,是两回事。
库的本质
把那层窗户纸捅了。
库,就是所有 .o 文件的集合。
换个问法,不用这些词,还能说清楚吗?------把一堆编译好的二进制目标文件打成一个包,给别人用。
库在文件系统里就是个普通文件。它有 inode,占数据块,跟图片、视频、可执行程序没区别。站在系统里看一眼:
bash
ls -l /lib64/libc*
# libc.so.6 -> 这是 C 语言动态库
# libc.a -> 这是 C 语言静态库(如果装了的话)
ls -l /usr/lib/x86_64-linux-gnu/libstdc++*
# libstdc++.so.6 -> C++ 动态库
# libstdc++.a -> C++ 静态库
后缀不一样而已:
| 平台 | 动态库 | 静态库 |
|---|---|---|
| Linux | .so |
.a |
| Windows | .dll |
.lib |
"库是文件"------这个认知很重要。因为文件 = 内容 + 属性,有 inode,在磁盘上占块。这些在文件系统那一章已经搞清楚了,今天注意力全放在内容上。
货物崇拜检测:如果你只是记住了
.a是静态.so是动态,但不知道它们就是 .o 的打包------那你只是在背命名,不是在理解。
还有一个问题值得一问:Linux 下的 C 库拷到 Windows 下能用吗? 不能。因为 Windows 和 Linux 编译出来 .o 文件的格式不一样。C 语言怎么解决这个问题的?它把每一套平台的代码在各个平台各自实现一份------功能一样,但编译出来的二进制格式各自适配。所以你装环境的时候要装 Linux 版的 C++ 标准库,也要装 Windows 版的------它们是不同的文件,互不相通。根本原因就是 .o 格式不同,这个后面看到 ELF 格式的时候会见分晓。
做一包静态库
你现在要当库的制作者。把两个 .c 变成 .o,再把 .o 打包成一个 .a。
bash
gcc -c my_stdio.c # → my_stdio.o
gcc -c my_string.c # → my_string.o
现在有一包 .o 了。打包工具叫 ar,全称 archive,归档。
bash
ar -rc libmyc.a my_stdio.o my_string.o
-r:replace。如果libmyc.a里已经有某个 .o 了,替换成最新的。-c:create。如果libmyc.a里没有某个 .o,新建进去。
合并起来的意思就是:打包,用最新的。 这不就是把 .o 归档嘛。
命名有规矩:必须以 lib 开头,以 .a 结尾,中间才是真正的库名。 动态库也一样------lib 开头,.so 结尾。
所以 libmyc.a 中的库名叫 myc。
把库产品化
你不能直接把头文件和 .a 文件一把扔给室友。你得包装一下------就像你安装的那些软件,它们都有自己的目录结构。
makefile
# Makefile 片段
lib=libmyc.a
src=$(wildcard *.c) # 获取当前目录所有 .c 文件。也可以用 $(shell ls *.c)
obj=$(src:.c=.o) # 把 .c 替换成 .o
CC=gcc
$(lib):$(obj)
ar -rc $@ $^ # $@ = 目标文件(libmyc.a), $^ = 所有依赖(所有 .o)
%.o:%.c
$(CC) -c $< # $< = 第一个依赖(对应的 .c 文件)
output:$(lib)
mkdir -p myc/include
mkdir -p myc/lib
cp *.h myc/include
cp *.a myc/lib
clean:
rm -rf *.o *.a myc
运行:
bash
make # 生成 libmyc.a
make output # 发布:创建 myc/ 目录,头文件进 include/,库文件进 lib/
现在你给别人交付的是这样的结构:
myc/
├── include/
│ ├── my_stdio.h
│ └── my_string.h
└── lib/
└── libmyc.a
还可以进一步打包压缩:
makefile
tgz:output
tar czf myc.tgz myc
bash
make tgz # 生成 myc.tgz,直接发给室友
站在使用者的角度用静态库
室友拿到了 myc.tgz,解压:
bash
tar xzf myc.tgz
现在他得写代码了。他写了个 main.c,包含头文件,调你的函数。然后编译。
第一次尝试:直接编译。
bash
gcc -c main.c
fatal error: my_stdio.h: No such file or directory
直接编不过。这是预处理阶段的错误 ------编译器在当前目录和系统 include 目录都找不到 my_stdio.h。因为头文件既不在当前目录,也不在系统目录。它在 myc/include/ 里面。
第二次尝试:告诉编译器头文件在哪。
bash
gcc -c main.c -I myc/include
-I(大写的 i)就是 include 路径。编译器除了在当前目录和系统 include 目录搜索,还会去你指定的路径搜。头文件找到了,main.o 出来了。
第三次尝试:链接。
bash
gcc -o myexe main.o
undefined reference to 'my_fopen'
undefined reference to 'my_string_len'
这是链接阶段的错误------编译器知道这些函数声明(头文件里有),但找不到它们的实现。因为你没告诉它去链接你的库。
第四次尝试:告诉编译器要链哪个库。
bash
gcc -o myexe main.o -l myc
/usr/bin/ld: cannot find -lmyc
还是报错------链接器找不到库文件本身 。GCC 默认只去 /lib64、/usr/lib64 这些系统路径搜库。你的 libmyc.a 在 myc/lib/ 下,它不知道。
第五次尝试:告诉编译器库在哪。
bash
gcc -o myexe main.o -L myc/lib -l myc
成了。-L myc/lib 的意思就是让编译器去 myc/lib 这个目录找库文件。
顺便说一下
-L .。如果库文件就在当前目录,没有产品化那套目录结构,你可以用-L .(.就是当前目录)。编译就不需要带那么长的路径。但正式交付还是用产品化的目录------规范。
记住三个选项------后面会反复用:
-I:头文件在哪找。预处理阶段用。-L:库文件在哪找。链接阶段用。-l myc:要链接哪个库。注意------去掉lib前缀和.a/.so后缀,只留中间的名字。libmyc.a→ 库名是myc。
带不带空格都可以:-l myc和-lmyc都行。
可以一步到位:
bash
gcc -o myexe main.c -I myc/include -L myc/lib -l myc
编译器去 myc/include 搜头文件,去 myc/lib 找 libmyc.a(当然也会试 libmyc.so),链接,形成 myexe。
为什么以前写 C 从来不用带这些选项?
因为你平时用的是 C 标准库。GCC 就是为 C 语言做的,它默认就知道 去哪找标准库(/lib64, /usr/lib64,其实还有 /usr/local/lib64),也知道库名叫 c(libc.so)。它心里给你偷偷加上了 -lc 和库路径。
但你自己的库是第三方库。GCC 不认识你这个小作坊出来的东西,你得自己报名字。
这里有个分类,把它搞清楚:操作系统自带的系统调用接口------一方库 ;C/C++ 语言标准库------二方库 ;除此之外任何人(包括你自己)提供的库------三方库 。凡是第三方库,未来都必须明确告诉 gcc 库的名字。名字绝不可商量。路径可以想办法------把库装到系统路径下就不用带
-L了,但-l必须带。
打个比方:你妈做饭,天然知道给你做什么。但你突然带个朋友回家,你妈不知道他要吃什么,也不知道有没有忌口,你得说。
这里有个容易搞错的地方:装了库到系统路径 ≠ 不用带
-l。路径不需要指明了,但库名必须报。因为 GCC 不知道你要链这个第三方库。除非你是 C 标准库------那是亲儿子。
把库安装到系统里
"安装"这个词听起来挺唬人。其实就是两件事:
- 把头文件拷到
/usr/include/ - 把库文件拷到
/lib64/
bash
sudo cp myc/include/* /usr/include/
sudo cp myc/lib/* /lib64/
装完以后,-I 和 -L 就可以省了:
bash
gcc -o myexe main.c -l myc
但还是得带 -l myc。因为这是第三方库,GCC 不认识你的 myc。
不建议把自己的野库装到系统里。你没经过测试验证的代码,污染系统路径,不是什么光彩的事。
更好的做法:让库待在你项目目录里,编译时老老实实带选项。顺便锻炼你操作 GCC 的能力。
库文件里能不能有 main 函数?
不能。
原因很简单:库提供功能,不提供入口。用户的 main.c 里有一个 main,你的库再来一个 main,链接的时候就冲突了------"multidefine"。
所以库永远只编译到 .o 就停,不继续往下链接。因为它没有 main,自己链不起来。它等着和用户的 main.o 一起链接。
做一包动态库 .so
先说一个前提:在实际开发中,使用动态库是最佳实践。 动态库的应用场景和范围最广。Linux 系统默认情况下甚至不给你装静态库------默认全用动态库。动态库有它无与伦比的优势(当然也有缺点),而且能很好地帮我们把大型项目模块化。
举个例子:假设你们公司在做一个大项目,光模块就 50 个。每个模块内部可能有上百个、上千个源文件。你只负责其中一个模块的一小部分。
最后怎么把几十个人的代码拼成一个大项目?做法是把每个人写的模块都编成动态库或静态库(.so 或 .a),然后由上层那个写调度逻辑的人,把所有模块的库链接到一起,形成完整的可执行程序。那个人的 main.c 里调各个模块的接口,编译时把所有人的库全链上。
所以你平常在 Windows 上安装软件时,除了那个 .exe,总能看到配套的一大堆 .dll(或 Linux 下的 .so)。原因就在这------它们就是各个模块打的动态库。
库不止是"站在巨人肩膀上"用的。多人协作时,库是用来拼模块的。
动态库是把 .o 打包,但不是用 ar,直接用 GCC 就行。
和静态库的差别就两处:
差别一 :编译 .c → .o 时,带 -fPIC。
bash
gcc -fPIC -c my_stdio.c
gcc -fPIC -c my_string.c
-fPIC:Position Independent Code,位置无关代码。
这里我没完全搞清楚------
-fPIC到底干了什么,为什么动态库必须用它?这个问题需要理解链接与加载之后才能回答。现在先当公式记着:做动态库,.c→.o必须加-fPIC。
差别二 :打包时用 -shared。
bash
gcc -shared -o libmyc.so my_stdio.o my_string.o
-shared 就是在告诉 GCC:别给我形成可执行程序,我要的是一个共享库(动态库)。
所以 Makefile 里静态库 vs 动态库的对比如下:
makefile
# 静态库
$(lib):$(obj)
ar -rc $@ $^
%.o:%.c
$(CC) -c $<
# 动态库
$(so):$(obj)
$(CC) -shared -o $@ $^
%.o:%.c
$(CC) -fPIC -c $<
注意:做动态库不需要新命令,GCC 自己就能搞定。动态库是亲儿子------系统默认用的就是它。(Linux 默认甚至不装静态库。)
用动态库------编译阶段跟静态库一模一样
室友按相同方式使用:
bash
gcc -o myexe main.c -I myc/include -L myc/lib -l myc
编译、链接,一路绿灯。跟静态库完全一样。
然后他运行:
bash
./myexe
./myexe: error while loading shared libraries: libmyc.so: cannot open shared object file: No such file or directory
炸了。
为什么静态库没事,动态库就炸了
这里有一个核心差别,必须搞清楚。不然你在动态库上的所有操作都是 cargo cult ------ 形式都做对了,但不知道为什么要这么做。
还记得网吧那个类比吗?
静态链接:你爸把网吧的电脑买回来装你宿舍了。从此你上网再也不用去网吧。你的可执行程序里,把库中用到的代码直接拷了一份进去。程序一跑,自带一切。不依赖任何外部库文件。
动态链接:学校不准带电脑。你必须去学校附近的电竞馆上网。程序运行时,代码还在电竞馆里------你需要的时候才去加载。
所以动态链接的可执行程序里没有库代码的拷贝 。它只有一条"我需要 libmyc.so"的记录。等你运行时,操作系统加载它时,发现它依赖 libmyc.so,操作系统得自己去把 libmyc.so 找到,加载进来。
关键来了:你在编译时用 -L 和 -l 告诉了编译器 库在哪儿。但程序是操作系统来运行的。操作系统不知道库在哪。
你跟编译器说了,你没跟操作系统说。操作系统一脸懵逼:你告诉谁了?
怎么确认是库找不到?
bash
ldd myexe
ldd:查看一个可执行程序依赖哪些动态库。
输出类似:
linux-vdso.so.1 => (0x00007ffc8fdf0000)
libmyc.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00007f1234560000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1234789000)
看到 libmyc.so => not found 了?这就是问题。
让操作系统找到你的动态库------四种方法
操作系统也有一套自己的默认搜索路径:/lib64、/usr/lib64 等。它只在这些地方找。
方法一:直接拷到系统目录
bash
sudo cp myc/lib/libmyc.so /lib64/
拷贝完,ldd myexe 就能找到,./myexe 正常运行。
不推荐------污染系统。
方法二:在系统目录下建软链接
bash
sudo ln -s /绝对路径/myc/lib/libmyc.so /lib64/libmyc.so
尽量用绝对路径,别用相对路径------省得以后路径上下文变了找不着。
删也方便。个人比较推荐。毕竟你删一个软链接比拷来删去干净。
方法三:环境变量 LD_LIBRARY_PATH
Linux 系统有一个环境变量,叫 LD_LIBRARY_PATH。它的作用是:操作系统找不到动态库时,去这个环境变量指定的路径再搜一遍。
注意:不同系统,或者同一系统的不同机器上,这个环境变量有可能不存在 。如果你用
echo $LD_LIBRARY_PATH查出来是空的------正常。有些同学的机器上它是存在的,因为你以前可能装过那个"一键配置 vim"的插件(vim-for-cpp),它内部也带了动态库,安装时自动给你导了这个环境变量。
添加路径(尽量用绝对路径):
bash
export LD_LIBRARY_PATH=/绝对路径/myc/lib:$LD_LIBRARY_PATH
然后:
bash
ldd myexe # 找到了
./myexe # 跑起来了
但这是临时的。 你关掉终端重新开,环境变量就没了。
想永久生效?写到配置文件里:
bash
# ~/.bash_profile 或 ~/.bashrc
export LD_LIBRARY_PATH=/绝对路径/myc/lib:$LD_LIBRARY_PATH
方法四:系统级配置文件 /etc/ld.so.conf.d/
在 /etc/ld.so.conf.d/ 目录下创建一个 xxx.conf 文件,里面只写一行:你的库路径。
bash
sudo bash
cd /etc/ld.so.conf.d/
echo "/绝对路径/myc/lib" > mylib.conf
ldconfig # 让配置热加载,立即生效
exit
然后 ldd myexe 就能找到。删掉这个 conf 文件再 ldconfig,效果就没了。
这种方法比较冷门。知道就行。
动静态库同时存在时,结果怎么样
这个不讲,你心里容易慌。我们直接把四种情况全跑一遍。
在你的 myc/lib/ 下同时放 libmyc.a 和 libmyc.so。
情况一:两个都在,不带 -static
bash
gcc -o myexe main.c -I myc/include -L myc/lib -l myc
ldd myexe
结论:优先动态链接。 ldd 里能看到依赖 libmyc.so。
情况二:两个都在,强制静态链接
bash
gcc -o myexe main.c -I myc/include -L myc/lib -l myc -static
结论:-static 强制静态链接。ldd myexe 里看不到 libmyc 的依赖了。file myexe 也会显示它是静态链接的程序。
情况三:只有动态库,你偏要 -static
编译报错。因为 -static 要求必须提供静态库。没有就是没有,不妥协。
情况四:只有静态库,不带 -static
bash
gcc -o myexe main.c -I myc/include -L myc/lib -l myc
ldd myexe # 神奇:看不到 libmyc
./myexe # 但能运行
它采用的是局部静态、整体动态 的策略。你的库虽然只有 .a,GCC 把你用到的部分拷进了可执行程序(局部静态),但程序依赖的 C 标准库等其他库仍然动态链接(整体动态)。
不用记这些排列组合。只需要记住一件事:优先动态链接。
用一个真的第三方库------ncurses
搞清楚了来龙去脉,现在玩一个真的第三方库:ncurses。这是一个终端字符界面编程库,能在黑窗口里画各种图案。
安装
bash
# CentOS
sudo yum install -y ncurses-devel
# Ubuntu
sudo apt install -y libncurses-dev
安装的本质:下载一个包,然后把头文件拷到 /usr/include/,把库文件拷到 /lib64/。
验证一下:
bash
ls /usr/include/ncurses* # 头文件在这里
ls /lib64/libncurses* # 库文件在这里
库叫 libncurses.so,所以去掉 lib 前缀和 .so 后缀,库名是 ncurses。
用起来
你不会写 ncurses 代码?没事,让 AI 帮你生成------告诉它"用 C 语言 + ncurses,写 30~50 行代码,画一个实时更新的 CPU 模拟图"。
AI 生成的代码不一定能跑,得自己调。这次生成的还凑合,编译的时候报了一堆 C99 兼容问题,加
-std=c99才过。
c
// test_ncurses.c
#include <ncurses.h>
int main() {
initscr(); // 初始化 ncurses
printw("Hello, ncurses!");
refresh(); // 刷新屏幕
getch(); // 等一个按键
endwin(); // 退出 ncurses
return 0;
}
编译:
bash
gcc -o nc_exe test_ncurses.c -std=c99 -l ncurses
只需要 -l ncurses。因为库已经装到系统路径下了,-I 和 -L 不需要。但 -l ncurses 必须带------这是第三方库。
运行:
bash
./nc_exe
屏幕上出现 "Hello, ncurses!",按任意键退出。效果嘛......有点矬,勉强能看。关键不是好不好看,是你搞清楚了一条完整链路------安装库 → 知道头文件和库文件在哪 → 写代码 → 编译带 -l → 跑起来。
所以到底发生了什么?就三件事
-
库就是 .o 的集合体。 把源码编译成 .o,然后打包,就是库。给你 .o 等价于给你库,给你库等价于给你一堆 .o。没什么好怕的。
-
静态库是 .o 用
ar打包成.a,动态库是 .o 用gcc -shared打包成.so。 编译时动态库多一个-fPIC,这是个"现在先记住,以后再说"的选项。使用上两者的编译选项一模一样:-I指定头文件路径,-L指定库路径,-l指定库名。 -
动态库运行时要让操作系统找到它。 四种办法:拷到系统目录、建软链接、设环境变量
LD_LIBRARY_PATH、写/etc/ld.so.conf.d/配置文件。本质上都是在告诉操作系统"这个目录里有动态库,你去那里找"。
还有一个隐藏的第四条:C 语言和 C++ 为什么头源分离?因为要 B 源。把 .c 编成库,只给头文件和库文件,别人就能用功能,但看不到你的源码。头源分离不是一种好的编码审美------它是一种分发策略。
站在应用开发的角度,今天讲的这些你如果都能搞定------从做库到用库,从静态到动态,从编译链接到运行时查找------已经够用了 。但如果要应付面试,面试官可能往下追问:动态库到底是怎么加载的?ELF 格式长什么样?-fPIC 到底干了什么? 这些问题需要对链接、加载、进程地址空间有更深的理解------那是下一层的事了。
从今天开始,你以前写的那些链表、队列、排序算法、字符串工具......把 main 函数删掉,头源分离,打包成库。下次别人找你要代码,你就能说:"代码不能给你,但功能可以用------拿这个 .a 或者 .so 去链接。"
有兴趣的话,去网上找一份 glibc 的源代码看看------里面全是 .c、.c、.c,最后把所有 .c 编成 .o,再打包成你看到的 libc.so。就是我们今天讲的。
就这么回事。