为什么 C 程序使用 libmath 的 .so 比使用 libmath 的 .a 显示更多的内存?

Why does a C program show more memory with .so of libmath than with .a of libmath?

根据 this C tutorial page 中的评论,我在一段时间不使用 C 后用来复习它,我希望使用共享版本的库编译的简单程序看起来会使用更少的内存比使用库的静态版本的那个程序的版本。

这是一个简单的示例程序,它请求用户输入,以便程序在我使用 topps 检查它时处于空闲状态。目标是只用 -lm(链接 libmath)编译程序,然后用 -lm --static 编译它。当我 运行 每个程序时,我应该看到静态选项占用与其 运行ning 进程相关的较少内存。

/* lib_a_vs_so.c */
#include <math.h>
#include <stdio.h>

void main()
{
    double x = sin(3.14);
    int user_input;

    printf("Enter a number: ");
    scanf("%d", &user_input);
    printf("You entered %d and sin(3.14) is %.2f\n", user_input, x);
}

编译两个不同版本的步骤:

c99 -o so_version lib_a_vs_so.c -lm
c99 -o a_version lib_a_vs_so.c -lm --static

和我系统上 top 的输出(gcc 版本 4.8.4(Ubuntu 4.8.4-2ubuntu1~14.04.1))当我 运行 这两个程序时。

top - 19:06:51 up 10:20,  5 users,  load average: 0.06, 0.24, 0.25
Tasks:   2 total,   0 running,   2 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.4 us,  0.8 sy,  0.0 ni, 96.4 id,  0.3 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:  16327932 total,  7522656 used,  8805276 free,   435692 buffers
KiB Swap:        0 total,        0 used,        0 free.  2836848 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                    
 7010 ely       20   0    4192    356    276 S   0.0  0.0   0:00.00 so_version                                                                 
 7025 ely       20   0    1080    264    212 S   0.0  0.0   0:00.00 a_version 

为什么 so_version 似乎使用了更多内存?

静态链接

当你静态地 link 一个库到一个二进制文件和 运行 二进制文件时,操作系统将只加载它实际需要使用的东西(当执行一条指令试图引用虚拟内存中未加载到物理内存的位置,发生页面错误并且 OS 加载页面并继续 -- ).[1]

动态加载

动态加载库时,操作系统使用mmap加载共享对象。[2] mmap自然也实现了按需分页, 所以整个对象并没有加载到物理内存中,但是它在多个进程之间共享,所以它的一部分可能已经加载到物理内存中了。

回答

我认为您的问题所针对的行为有两种可能性(不一定相互排斥):

  1. (更有可能,IMO)当 top 计算内存使用时,它将共享对象 space 计为进程正在使用,其中包括整个共享对象 space这是物理加载的,即使这个特定的程序不需要使用它。这将使此进程使用的内存看起来比它的静态 linked 对应物大,后者的物理加载的静态 linked 库更少。
  2. (不太可能,IMO)为共享库映射的地址 space 保证包含整个库,因为任何时候都可能弹出一个需要使用共享库任何部分的新程序图书馆。但是,在静态 linking 中,linker 可能会跳过编译单元,如果它们从未被正在 linked 的程序的其余部分引用(source)。这可能会导致静态库的大小在 linking 期间减少(因此,在 运行 时间内内存使用量减少)。

[1] 这适用于所有主要操作系统(Linux-based, BSD-based (macOS, iOS ), 和 Windows).我确定有一个 OS 在 运行 之前将整个二进制文件加载到内存中,但这超出了这个答案的范围。

[2] 这个细节是我知道 Linux 和 FreeBSD 做的。 Windows 可能是用 DLL 实现的,但我不确定。