V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
nnegier
V2EX  ›  C

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

  •  2
     
  •   nnegier · 48 天前 · 4513 次点击
    这是一个创建于 48 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

    42 条回复    2024-03-25 20:14:47 +08:00
    passive
        1
    passive  
       48 天前 via Android
    静态或者动态库和 c 没关系,每个操作系统规则都不太一样。

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

    GeruzoniAnsasu
        3
    GeruzoniAnsasu  
       48 天前
    系统学习: 看这本书 https://book.douban.com/subject/3652388/

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

    综上所述,类比一下,c 程序是猪,如果是静态编译,那这就是头变异猪,否则就是个母猪,至于你知道如何调用这些公猪母猪,那你就是养猪场老板,关系能明白了吗?
    rabbbit
        21
    rabbbit  
       48 天前
    @cnbatch
    许可证的原因是指 QT 这种要求必须使用动态库才可以商业使用吗?
    MrKrabs
        22
    MrKrabs  
       48 天前
    第三方软件出漏洞了更新下动态库就好了
    lolizeppelin
        23
    lolizeppelin  
       48 天前
    假设你写了一个程序叫 nginx
    你的程序依赖了以下库
    ssl/gif/jpg/png/lua/gjson

    如果你使用静态方式引用上述库
    ssl 库更新时 你要重新编译 nginx,更新你的程序
    gif 库更新时, 你要重新编译 nginx,更新你的程序
    gif 库更新时, 你要重新编译 nginx,更新你的程序
    ....等等


    如果你使用动态库方式引用上述库
    ssl 库更新时,你只需要更新 ssl 库,不需要更新你的程序
    ssl 库更新时,你只需要更新 gif 库,不需要更新你的程序
    ...等等


    假设你的程序代码 1KB
    ssl/gif/jpg/png/lua/gjsn 每个库都是 10MB

    如果你使用动态库,你的程序编译出来就是 1KB,内存最低 1KB (简单计算)

    如果你使用静态库,你的程序编译出来就是 600MB+1KB,内存最低 600MB+1KB (简单计算)
    thevita
        24
    thevita  
       48 天前   ❤️ 1
    我看 op 的主要疑惑应该是在于,用了 Android 的应用模型来理解 so 的“共享”的作用,这里的应用不一定是 android 应用

    这里 op 可以看看传统 linux/windows 的 应用模型就比较好能搞明白 (因为这本身设计也主要是基于此的),以 linux 来说,应用(包含应用主程序) 和 库(包含一系列 so )是分开安装和管理的,同一个应用可能依赖相同的库,这个相同的部分就不用重复安装了

    Android 正常的开发模式下(指 用 ndk 编译好 so 打包进 apk 中进行分发),因为 Android 应用沙河的存在,共享这个 so 是做不到的,这里 so 的作用就是用来实现动态加载,对接 JNI 的 api 而已.
    cnbatch
        25
    cnbatch  
       48 天前   ❤️ 2
    @rabbbit 差不多。但凡想要用 LGPL 的库却又不想开源自家源码,那就只能动态链接,用动态库形式。
    thevita
        26
    thevita  
       48 天前
    @thevita "同一个应用可能依赖相同的库,这个相同的部分就不用重复安装了" => "不同应用可能依赖相同的库,这个相同的部分就不用重复安装了"
    meiyiliya
        27
    meiyiliya  
       48 天前
    我自己学了皮毛 Android APP 开发,我记得 WebView 就是 Android 自带的一个库,App 可以选择调用系统的 WebView ,也可以自己内嵌一个 Web 引擎。
    前者就是动态库,N 个 APP 都可以使用同一个 WebView ,只要系统的 WebView 升级了,APP 自己是可以不动。
    后者就是静态库,N 个 APP 要独立内嵌 N 个 Web 引擎,即使 Web 引擎一样也要占 N 份空间,且升级时也要独立升级。
    fuckshiter
        28
    fuckshiter  
       48 天前
    我也有个问题,系统怎么知道这个动态库是同一个,大家都叫一样名字的话,例如 system.loadLibrary xxx.so
    iorilu
        29
    iorilu  
       48 天前
    静态就是相当于直接拷贝代码合并到你代码一起编译

    动态就是调用现成的模块, 不会和你代码合并一起
    noahlias
        30
    noahlias  
       48 天前
    @fuckshiter 有加载顺序 我之前就是不小心设置错了 导致我的 mpv bug 了哈哈 我还为此去提 issue 找 bug 搞了好久哈哈哈
    laminux29
        31
    laminux29  
       48 天前
    1.共享库就是可以给多个程序公用的公共函数集合,优点是只占一份空间,缺点是一旦被更改,依赖它的多个程序就完蛋。静态库相反。

    2.共享库的出现,是因为以前磁盘、内存、带宽贵。
    seeker
        32
    seeker  
       48 天前
    静态,动态到处都会用这两个词,一般来说
    静态=编译时,运行前 compile time
    动态=运行时 runtime
    比如一个库,静态库就是编译前把代码(编译好的)跟你的代码打包到一块,形成一个文件。动态库,就是不把代码跟你的代码打包成 1 个文件,而是在程序运行时,把库的代码载入。
    其他地方的静态,动态,基本都类似意思。
    qping
        33
    qping  
       48 天前
    @hello2090 #10 你说的就很容易懂,OP 的那段描述真的是云里雾里
    nnegier
        34
    nnegier  
    OP
       48 天前
    @hello2090 #11 那 Chrome 和 Safari 怎么判断那个动态库是一样的呢?
    ecnelises
        35
    ecnelises  
       48 天前
    @nnegier
    可以用命令打出系统/Applications 目录下的应用程序的可执行文件依赖的动态库,比如 Firefox 就是 otool -L /Applications/Firefox.app/Contents/MacOS/firefox ,你会发现大部分软件都依赖了一大堆动态库,都是同样的系统路径
    WuSiYu
        36
    WuSiYu  
       48 天前   ❤️ 1
    主要是如今除开系统都会带的那些库,对于很多 windows 、mac 应用乃至 android app 而言,其实没有共享不共享的区别,因为他们都会随着安装包带着自己的一堆动态库,每个程序的所有文件是放在一起的,相互间很少会引用,无非是分成多个文件和集成到一个文件的区别
    但对于 linux 下,用包管理器装一个软件,你会发现这个软件是打散放置在根目录下的,所有的动态库(.so )基本都会放在/lib 、/usr/lib 这种地方,在系统的标准路径里,那么其他程序也就可以用这些库,比如 debian 下 ttyd 依赖 libjson-c5 ,另一个别的软件包也会依赖 libjson-c5 ,那么此时 libjson-c.so.5 就只用存一个,这两个软件包共享这个动态库,同时运行的话在内存里也可以只加载一份,这样自然是更高效的,但必须要有一个统一的软件源(对于每个 linux 发行版而言),每个应用都得由软件源的维护者人工填好这些依赖信息,否则就没法保证你需要的这个库系统里有
    hello2090
        37
    hello2090  
       48 天前 via iPhone
    @nnegier 不用想这么多,chrome 和 safari 的开发者知道他们用哪个库,用哪个版本。就像你需要用一个第三方的库,你肯定知道哪个版本,或者说你肯定指定了一个版本。只要这两个是同一个东西,系统就能保证。
    thedinosaurmail
        38
    thedinosaurmail  
       47 天前
    静态库就是每次进程启动都要塞进内存里面, 所以每个进程都有一模一样的内容
    动态库就是共享内存 share 这块内容 ,这样可以减少资源
    xwwsxp
        39
    xwwsxp  
       47 天前
    静态库,就是将所有东西都打包到一起,带来的问题就是文件膨胀;也有好处,不太容易出现启动失败;
    但是,动态库,可以参考 win 的 dll 设计了,Linux 中就是 so ,动态链接库,就是将相同功能的东西放到固定的目录,这样对于软件开发者来说,打包就没有静态库那么大;但是,一旦动态库或动态链接库出现丢失,可能很多软件就跑不了。
    不过,静态库和动态库,一般都是针对 C 或 C++ 等项目;对于 Java 而言,压根不存在~
    lopo
        40
    lopo  
       46 天前
    大概意思就是:静态库是复制粘贴,动态库是超链接
    junwind
        41
    junwind  
       46 天前
    sztink
        42
    sztink  
       33 天前
    总结一下动态库存在的意义有:
    1. 代码复用和模块化:动态库提供了一种代码复用的机制,可以将常用的功能封装在库中,多个程序可以共享同一个库,避免了重复编写相同的代码,提高了开发效率。

    2. 节省内存空间:多个程序可以共享同一个动态库文件,这意味着在内存中只需要加载一份动态库代码,而不是每个程序都有一份拷贝,因此可以节省内存空间。

    3. 简化更新和维护:如果需要更新动态库中的功能或修复其中的 bug ,只需要更新动态库文件,而不需要重新编译和部署整个程序。这简化了更新和维护过程,减少了可能的错误。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   906 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 569ms · UTC 22:15 · PVG 06:15 · LAX 15:15 · JFK 18:15
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.