8

在学习 C 的同时做一些实验时,我遇到了一些奇怪的事情。这是我的程序:

int main(void) {sleep(5);}

编译时,可执行文件的文件大小为 8496 字节(与 26 字节的源代码相比!)这是可以理解的,因为调用了 sleep 并且在可执行文件中写入了调用指令。另一点是没有睡眠,可执行文件变成 4312 字节。

int main(void) {}

我的主要问题是运行第一个程序时会发生什么。我使用 clang 编译和 Mac OS X 运行它。结果(根据活动监视器)是程序使用了 504KB 的“真实内存”。为什么程序只有 4KB 就这么大?我假设可执行文件已加载到内存中,但除了睡眠调用之外我没有做任何事情。为什么我的程序需要 500KB 才能休眠 5 秒?

顺便说一句,我使用睡眠的原因是能够首先使用活动监视器捕获正在使用的内存量。

我只是出于好奇而问,干杯!

4

2 回答 2

7

当你编译一个 C 程序时,它被链接到一个可执行文件中。即使您的程序非常小,它也会链接到 C 运行时,其中将包含一些额外的代码。可能会有一些错误处理,并且此错误处理可能会写入控制台,并且此代码可能包含sprintf为您的应用程序添加一些占用空间的代码。您可以请求链接器在可执行文件中生成代码映射,以查看实际包含的内容。

此外,可执行文件包含的不仅仅是机器代码。将有各种用于数据和动态链接的表,这将增加可执行文件的大小,并且也可能会浪费一些空间,因为各个部分都存储在块中。

C运行时将在调用之前初始化main,这将导致加载一些代码(例如通过动态链接到各种操作系统功能)以及为堆分配内存,每个线程的堆栈以及可能还有一些静态数据. 并非所有这些数据都可能显示为“真实内存”——OS X 上的默认堆栈大小似乎是 8 MB,而您的应用程序使用的仍然比这少得多。

于 2013-10-14T09:36:36.437 回答
2

在这种情况下,我认为您观察到的大小差异很大程度上是由动态链接引起的。

链接器通常不会将通用代码放入可执行二进制文件中,而是保留信息,并在加载二进制文件时加载代码。在这里,这些通用代码存储在名为shared object(SO)or的文件中dynamically linked library(DLL)

[pengyu@GLaDOS temp]$ cat test.c
int main(void) {sleep(5);}
[pengyu@GLaDOS temp]$ gcc test.c
[pengyu@GLaDOS temp]$ du -h --apparent-size a.out
6.6K    a.out
[pengyu@GLaDOS temp]$ gcc test.c -static
[pengyu@GLaDOS temp]$ du -h --apparent-size a.out
807K    a.out

另外,在这里我列出了进程内存中的内容:

  • 需要加载必要的动态库:

    这里ldd给出了调用二进制文件时要加载的动态库的结果。这些库位于通过调用mmap系统调用获得的部分。

    [pengyu@GLaDOS temp]$ cat test.c
    int main(void) {sleep(5);}
    [pengyu@GLaDOS temp]$ gcc test.c
    [pengyu@GLaDOS temp]$ ldd ./a.out
    linux-vdso.so.1 (0x00007fff576df000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007f547a212000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f547a5bd000)
    
  • .data,这样的部分.code被分配给二进制文件中的数据。

    这部分存在于二进制可执行文件中,因此大小应该不大于文件本身。在可执行二进制文件的加载阶段复制的内容。

  • 在程序执行期间,有一些部分.bss和堆栈区域要分配以供动态使用。

    这部分在二进制可执行文件中不存在,因此大小可能非常大,而不受文件本身大小的影响。

于 2013-10-14T10:19:49.570 回答