C,C ++:共享库:是单个函数还是完整库加载到内存中?

5

使用静态编译时,只有程序实际需要的库函数会被链接到程序中。那么对于共享库呢?动态链接器是否只会将程序实际需要的函数加载到内存中,或者总是加载整个共享库?如果是函数,我该如何在运行时获取程序及其加载函数的实际大小?

谢谢! Oliver


2
如果库是共享的,它们可能会被多个程序同时使用。你如何计算这个? - Bo Persson
1
当共享库被加载到内存中时,它是否会增加可执行文件的大小?谁知道呢?共享库可以被加载到内存中并被所有应用程序重复使用(在相同位置)。即使你找到了答案,也不意味着它将来会维持不变,每个操作系统都可以改变和优化处理共享库的方式。这远远超出了任何语言规范的范畴,完全落在操作系统的领域。 - Martin York
@mu太短了:不,我认为你错了,考虑到g++和ld,这是我从自己的经验中猜测的。没有特殊选项,链接器将切掉未使用的符号。请查看此问题以获取更多信息。 - beduin
感谢大家的评论。我对此很感兴趣,因为我对优化嵌入式Linux系统很感兴趣。如果我理解正确的话,静态库的大型目标文件实际上是不好的,因为可能有很多函数是可执行文件的一部分,但从未被使用过?当然,你们Bo和Martin是正确的,如果一个库被多个进程共享,那么大小的问题是没有意义的。让我们假设只有一个这样的进程。 - Oliver
好的,我承认错误。历史上,链接器很蠢,因此采用了老派的“每个文件一个函数”的技巧。幸运的是,我在过去十年中都没有不得不静态链接任何东西。 - mu is too short
显示剩余2条评论
2个回答

8
使用静态编译时,只有程序实际需要的库函数才会链接到程序中。而共享库呢?
程序通过符号引用共享库,也就是说,程序将通过名称识别与其链接的共享库。
动态链接器是否只会将程序实际需要的函数加载到内存中,还是总共享库总是被加载?
程序将会引用共享库中特定的入口点和数据对象。共享库将作为单个大对象映射到内存中,但只有实际引用的页面才会被内核分页。加载的共享库总量将取决于引用密度、其他链接到它的图像的引用以及库自身功能的局部性。
如果是函数,如何在运行时获取程序包括其已加载函数的实际大小?
在Mac和其他基于Unix的系统上,最好的方法是使用ps(1)。

非常感谢您的回答!如果我理解正确的话,那么只有包含所需函数代码的页面才会被加载。这是否意味着对于嵌入式系统,如果我只有很小的RAM但是有大型库(可能无法全部适应RAM),我仍然可以在Linux中运行可执行文件? - Oliver
是的,这就是它的意思(如果您认为它回答了您的问题,请记得批准答案)。尽管您确实应该为最坏情况做好计划,并确保无论调用哪个依赖函数,都不会耗尽内存。 - Joseph Lisee
@Oliver:也许吧。因为页面粒度较粗,动态图像会加载很多你从未使用过的位,而库本身可能会触及比“hello, world”更多的功能。这是一个困难的权衡,静态链接对于一个应用程序使用更少的内存,但它意味着库不能与其他单独链接的程序和系统守护进程共享。我会静态链接仅使用一次的任何库,并动态加载那些由基本服务共享的库。请注意,您可以运行多个相同的静态映像而不会有内存惩罚。 - DigitalRoss

2
当你进行静态链接时,只有潜在调用的函数会被链接到可执行文件中--但在运行时,可执行文件中的数据将通过需求分页被读入内存。
当进程被创建时,为该进程中的所有可执行代码和共享库分配地址,但文件中的代码/数据并不一定在这个时候读入物理内存。当你尝试访问当前未在物理内存中的地址时,它将触发一个不存在异常。操作系统虚拟内存管理器将根据此来读取页面从文件到物理内存,然后让访问继续进行。
加载是按页为单位进行的,通常每次以4或8千字节的块为单位(例如,x86使用4K页面,Alpha使用8k)。x86也具有创建更大(4兆字节)页面的能力,但这些通常不用于正常代码--它们用于映射保持映射(半)永久的大块内存,例如典型图形卡上的“窗口”,使其可以直接被CPU访问。
大多数加载器都有一些优化,因此(例如),它们在程序最初启动时会尝试读取更大的内存块。这使得它比在访问每个代码页面时产生的中断和单独读取更快。该优化的确切细节因操作系统(甚至同一操作系统的不同版本)而异。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接