【问题标题】:Why does a C program show more memory with .so of libmath than with .a of libmath?为什么 C 程序使用 .so 的 libmath 比使用 .a 的 libmath 显示更多的内存?
【发布时间】:2018-07-01 17:06:16
【问题描述】:

基于this C tutorial page 中的 cmets,我在一段时间未使用 C 后使用它来审查 C,我希望使用共享版本库编译的简单程序似乎比 C 版本使用更少的内存该程序使用库的静态版本。

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

/* 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 似乎使用更多内存?

【问题讨论】:

  • 很简单地说,如果你使用静态库,你只能从中得到你需要的东西。如果您使用共享库,您将获得全部内容,您通常会与系统上碰巧使用同一库的任何其他进程共享它。这看起来是更多还是更少的内存在某种程度上取决于有多少其他进程正在使用它,以及共享内存的计算方式。
  • 啊,有道理。我现在还看到,这两个可执行文件的磁盘占用空间确实与直觉相符。 a_version 为 868k,但so_version 仅为 20k。这个磁盘大小是否反映了整个 libmath.a,或者只是与 sin 以及宏/其他相关的一部分?
  • 你的程序不仅使用了libm,还使用了libc。
  • @Deduplicator 这让我更加困惑。当我尝试使用c99 -o a_version lib_a_vs_so.c /usr/lib/x86_64-linux-gnu/libm.a 时,现在两个可执行文件在磁盘上的大小相同,均为 20K(.so 版本的原始大小)。为什么将动态 libc 与静态 libm.a 结合使用会导致与两个库都是动态的大小相同?
  • 好吧,你的编译器知道 sin(),所以它可以在编译时执行它,或者内联它,或者其他什么。这意味着实际上没有使用来自 libm 的符号,因此对它的所有引用都被丢弃...

标签: c gcc memory shared-libraries static-libraries


【解决方案1】:

静态链接

当您将库静态链接到二进制文件并运行二进制文件时,操作系统将仅加载它实际需要使用的内容(当指令的执行尝试引用未加载到的虚拟内存中的位置时)物理内存,发生页面错误,操作系统加载页面并继续 -- helpful)。[1]

动态加载

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

回答

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

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

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

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

【讨论】:

    猜你喜欢
    • 2019-05-11
    • 2015-12-22
    • 2018-01-24
    • 2011-03-31
    • 2012-07-24
    • 2021-09-20
    • 1970-01-01
    • 2018-09-29
    相关资源
    最近更新 更多