还是不太理解 C 静态库和动态库?

59 天前
 nnegier

我看到一个描述,“为了方便,我们需要把 moon.o 、sun.o 、earth.o 这 3 个东西弄成一个东西。这种方法 就是 静态库 跟 动态库,静态库 是可以链接进去 程序自身,动态库是共享库,可以由多个程序共享节省空间,动态库只有用到的时候才会加载。”

它这里的程序是指单个应用还是指单个 C 语言文件呀?如果是单个应用,怎么说多个程序共享呢;如果是说单个 C 文件,那所谓的共享又是指啥呢;反正我横竖都弄不清,只知道都能用。(我个人背景是上层 Android 开发,使用的时候感觉没啥太大区别,当然了静态库快、动态库慢这个我能理解,像空间节省这个我没能理解)

4631 次点击
所在节点    C
42 条回复
passive
59 天前
静态或者动态库和 c 没关系,每个操作系统规则都不太一样。

不严格地说,同一个动态库被加载之后只在内存里存一份,如果硬件支持内存管理,会被映射到调用进程的各自内存空间;不同进程都能共享这同一份被加载的库。或者还有一种场景是同个进程各个被调用的库循环依赖某个动态链接库,也只需要加载一份。
passive
59 天前
前段时间看到 Shilon 大佬的视频:

<amp-youtube data-videoid="_enXuIxuNV4" layout="responsive" width="480" height="270"></amp-youtube>
GeruzoniAnsasu
59 天前
系统学习: 看这本书 https://book.douban.com/subject/3652388/

概念速成: 静态库=可以 **复制粘贴** 的一堆二进制代码; 动态库=可以 **动态引用** 的一堆二进制代码。
junyee
59 天前
所谓的共享,是指最终的二进制文件而言的。
如 moon.o 、sun.o 、earth.o ,生成静态库 1.a, 生成动态库 1.so.

main.c 使用静态库 生成二进制文件可以独立执行且文件更大,
使用动态库编译后生成文件更小,但需要依赖 1.so ,好处是有另外程序依赖时可以节省总体文件体积。
timothyye
59 天前
这里的程序就是指编译后的可执行文件。共享是指一部分代码被编译成动态库之后能被一个或者多个程序在运行时动态加载,从而达到代码共享的目的。动态库实际上是二进制层面的代码共享。
hazardous
59 天前
这里的程序是指编译打包后的可执行文件,空间指的是硬盘空间。
ajaxgoldfish
59 天前
3 楼说的对。要明白这里面放的是什么,为什么有静态库还要让动态库存在。为什么用动态库的时候还要用导入符号(lib),搞清楚几个概念函数签名符号,美团博客有一篇装载与链接,搞清楚之后你就会明白常见的错误 LNK2019 LNK2001 之类的了。
ajaxgoldfish
59 天前
totoro52
59 天前
这个是历史遗留问题了,以前硬盘贵,才这么玩。
hello2090
59 天前
有啥不理解的,静态库会编译成执行文件里去啊。动态库本身是个文件,多个程序可以共用它不就节省空间了吗?
hello2090
59 天前
单个应用为啥不能共享了,你一个 chrome 跑起来是一个应用,一个 safari 跑起来是一个应用。他们可以共用一个动态库呗
ecnelises
59 天前
粗略地讲,一个「程序」主要由两个部分构成:供 CPU 执行的代码区域和供代码读写的数据区域。对应地,每个.c .cpp 文件编译后的.o (Windows 上是.obj) 文件也有自己的代码区域和数据区域,最后由 ld (Windows 上是 link.exe) 把所有的代码区域合并为一个,数据区域也合并为一个,然后调整好里面引用的位置偏差。

大多数情况下,每个程序的数据区域都可以由这个程序任意读写,但代码区域是只读的。程序运行的时候,代码区域和数据区域都会被加载到内存中。顺着这个思路,假如系统里有很多个程序链接了一样的库,那每个链接了这个库的程序只需要复制一份库的数据区域以读写就行了,代码区域反正是只读的,内存里只用存一份,大家执行代码时都指向这个区域就行。因此,使用动态库可以减少内存占用,就是指这种情况下节省了多余的代码区域。

另外,很多程序会直接链接系统某个路径的动态库,运行的时候不同程序直接读取系统路径的就可以,而用静态库的话,每个程序都会把这个库被引用到的所有内容都打包进可执行文件里。所以动态库也可以减少磁盘空间占用。
bbxiong
59 天前
动态连接
拿 windows 平台举例,ntdll.dll kernel32.dll 等这些 dll 每个 exe 进程都会加载,windows 会给每个进程映射相同的系统 dll,内存属性为 PAGE_EXECUTE_WRITECOPY,PAGE_WRITECOPY,所以每个进程的 ntdll kernel32 模块基址都是相同的,这种 dll 在进程中是不占用进程内存,除非修改了数据

静态连接
就相当于把用到的代码 copy 到你的程序中,用相同静态库的进程越多,就越造成内存浪费,好处是没有外部库依赖


不过现在内存硬盘都不小,用静态库减少依赖部署起来更方便
draymonder
59 天前
我也有个问题想请教,一个程序编写好后,和动态库一起编译。

这个动态库的代码地址在运行前是已经确定好了,还是运行时由 os 分配的地址?
clino
59 天前
举一个具体例子:libssl ,比如你的程序里要用到 openssl 的加密功能,那么可以调用动态链接库里的接口,否则你还要自己实现一份 openssl ,或者在你的程序里把 openssl 代码编译进去,openssl 的升级也要你自己去做
ecnelises
59 天前
@draymonder
动态库在内存中的地址是不确定的,加载的时候才会分配地址空间,甚至对每个符号来说,它们的地址要到第一次被调用时才确定(延迟绑定),只是操作系统有虚拟内存机制,所以不同进程地址空间里的动态库地址实际上指向的是同一份。
kaiserzhang123
59 天前
其实可以这样理解,每个 c 编写的应用程序(库)的”所有逻辑/实现“最终都会打包成堆栈( heap/stack )模型中的一个单元,而每个单元呢都有其自己所在的地址。

静态库是在启动应用时就一起加载到内存中,已经在编译阶段知道了其所在的地址,所以在调用库中的某个单元时(数据/实现),机器直接把其地址推入栈( stack )中运行。

动态库是按需加载,共享整个库的堆,每个程序想要调用时则需要先通过动态连接器并获取库对应的单元地址(实现不同,原理差不多)并将这个单元地址推入栈中运行。
cnbatch
59 天前
@totoro52 不完全是“硬盘贵”的缘故,还有许可证的原因。
leimao
59 天前
@totoro52 不光是硬盘,还有内存
mylovesaber
59 天前
楼上说法太专业,理解所需知识储备下限很可能高于楼主知识积累的上限,用一种低俗但可能比较直观的说法来解释吧:

公猪配种这词你听说过吧?自然交配公猪比例为每 15 ~ 20 头母猪准备 1 头公猪。想要 20 头母猪怀孕,需要的是公猪的精子。而公猪本身就是储存精子的容器(库)
那么这个公猪就是动态库,有公猪的情况下,母猪就是程序。

母猪想怀孕,就会在排卵期去叫公猪过来上她(程序调用了动态库)。

而养猪场配比上面提到了,想要 20 头母猪怀孕,不需要准备 20 头公猪,而是让一头公猪的精液平分 20 份给到母猪即可(或者说是让公猪对着 20 头母猪依次上一遍):
从母猪角度来说,多个母猪实际上是共享了一头公猪;
从公猪角度来说,就是公猪是共享猪,可以由多头母猪共享(动态库是共享库,可以由多个程序共享)

为啥 20 头母猪共享一头公猪?因为一头公猪就能完成对 20 头母猪的配种任务,就没必要准备 20 头公猪来 1v1 ,因为养 20 头公猪所消耗的猪饲料是 20 份,养一头公猪所需猪饲料只需 1 份,这样就可以节省存放饲料所占用的仓库空间(动态库可以由多个程序共享节省空间)

母猪配种一般是季节性的,就是每年都有几轮集中性配种,其他时间没有配种任务,就用不着公猪了(动态库只有用到的时候才会加载)

如果天生异象,出现了一头变异猪,雌雄同体,那么配种这事情它一头猪就可以完成,不需要让其他公猪跟她配对,那么这个变异猪的公猪身体结构的部分就是静态库,这头猪整体就是个静态程序

综上所述,类比一下,c 程序是猪,如果是静态编译,那这就是头变异猪,否则就是个母猪,至于你知道如何调用这些公猪母猪,那你就是养猪场老板,关系能明白了吗?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1022210

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX