【问题标题】:Linking with gcc increases file size to 16 KB使用 gcc 链接将文件大小增加到 16 KB
【发布时间】:2021-12-19 18:40:08
【问题描述】:

我现在在 linux 上。我正在编译一个超级简单的 C 程序:

#include <stdio.h>
int main()
{
    printf("Hello, world!\n");
    return 0;
}

并编译 gcc main.c -o main

在运行ll 以获取文件大小后,它会返回以下内容:

-rwxr-xr-x 1 xylight xylight  16K Nov  5 11:30 main
-rw-r--r-- 1 xylight xylight   68 Nov  5 11:23 main.c
-rw-r--r-- 1 xylight xylight 1.5K Nov  5 11:30 main.o

链接main.o后,文件大小变为16KB!我怎样才能让这个更小?任何链接器选项?

我不确定这是否是重复的,我在这里找不到任何东西。让我知道它是否重复。

运行 readelf -h main 后,它说 ELF 类型是这样的:DYN (Shared object file)

有谁知道我可以做些什么来使这个更小?

【问题讨论】:

  • elf 可执行文件包含的内容远不止您编写的代码。例如,它与 C 运行时代码 (crt0.o) 链接
  • printf() 具有广泛的功能,并且由于它的大小,一些嵌入式编译器仅将其作为选项包含。
  • 你为什么关心文件有多大?我希望所有这三个文件都占用 32Kbytes 的磁盘空间。
  • @WeatherVane 请注意,printf 实现未链接到可执行文件中(它驻留在libc.so.6 中),因此不会增加开销。
  • 我想在某个地方我们有一个规范的“为什么我编译的可执行文件这么大”的问题,但我现在找不到。

标签: c gcc linker


【解决方案1】:

我无法在 SO 上找到一个很好的解释,所以让我在这里发布一个。

首先,默认情况下,可执行文件包括用于调试的静态符号表,在运行时不加载。我们可以使用strip main 摆脱它,这将为我们节省大约 2K(在我的 Ubuntu 20 上降至 15K)。

现在,我们可以通过运行readelf -SW main 来仔细查看开销:

  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000000318 000318 00001c 00   A  0   0  1
  [ 2] .note.gnu.property NOTE            0000000000000338 000338 000020 00   A  0   0  8
  [ 3] .note.gnu.build-id NOTE            0000000000000358 000358 000024 00   A  0   0  4
  [ 4] .note.ABI-tag     NOTE            000000000000037c 00037c 000020 00   A  0   0  4
  [ 5] .gnu.hash         GNU_HASH        00000000000003a0 0003a0 000024 00   A  6   0  8
  [ 6] .dynsym           DYNSYM          00000000000003c8 0003c8 0000a8 18   A  7   1  8
  [ 7] .dynstr           STRTAB          0000000000000470 000470 000082 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          00000000000004f2 0004f2 00000e 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         0000000000000500 000500 000020 00   A  7   1  8
  [10] .rela.dyn         RELA            0000000000000520 000520 0000c0 18   A  6   0  8
  [11] .rela.plt         RELA            00000000000005e0 0005e0 000018 18  AI  6  24  8
  [12] .init             PROGBITS        0000000000001000 001000 00001b 00  AX  0   0  4
  [13] .plt              PROGBITS        0000000000001020 001020 000020 10  AX  0   0 16
  [14] .plt.got          PROGBITS        0000000000001040 001040 000010 10  AX  0   0 16
  [15] .plt.sec          PROGBITS        0000000000001050 001050 000010 10  AX  0   0 16
  [16] .text             PROGBITS        0000000000001060 001060 000185 00  AX  0   0 16
  [17] .fini             PROGBITS        00000000000011e8 0011e8 00000d 00  AX  0   0  4
  [18] .rodata           PROGBITS        0000000000002000 002000 000012 00   A  0   0  4
  [19] .eh_frame_hdr     PROGBITS        0000000000002014 002014 000044 00   A  0   0  4
  [20] .eh_frame         PROGBITS        0000000000002058 002058 000108 00   A  0   0  8
  [21] .init_array       INIT_ARRAY      0000000000003db8 002db8 000008 08  WA  0   0  8
  [22] .fini_array       FINI_ARRAY      0000000000003dc0 002dc0 000008 08  WA  0   0  8
  [23] .dynamic          DYNAMIC         0000000000003dc8 002dc8 0001f0 10  WA  7   0  8
  [24] .got              PROGBITS        0000000000003fb8 002fb8 000048 08  WA  0   0  8
  [25] .data             PROGBITS        0000000000004000 003000 000010 00  WA  0   0  8
  [26] .bss              NOBITS          0000000000004010 003010 000008 00  WA  0   0  1
  [27] .comment          PROGBITS        0000000000000000 003010 00002a 01  MS  0   0  1
  [28] .shstrtab         STRTAB          0000000000000000 00303a 00010a 00      0   0  1

如您所见,前 1.5K(最多但不包括 .init)包含用于加载共享库(所有这些 .gnu.hash、.dynsym 等)的 ELF 标头和簿记数据。

后面是 500 字节的代码(.init、.plt 等,直到 .rodata,但不包括在内)。请注意,代码分配从 4K 页面边界开始,因此我们浪费了 2.5K 用于填充。

然后浪费了 3.5K 来重新对齐 4K 页面边界处的代码,然后再调整大约 1K 的数据段(.rodata、展开表等)。 .eh_frame 和 .init_array 之间存在有趣的 3K 浪费,这是由于只读数据和普通数据之间存在一些奇怪的对齐方式(有关详细信息,请参阅 this question)。

总而言之,只有一小部分 ELF 大小(1.5+0.5+1=3K 即 20%)被真正使用,其余的被浪费在 ELF 被mmaped 到内存时正确对齐地址。需要地址对齐,以便动态加载器可以为内存页分配不同的权限(例如,代码页不能写入但可以执行,而数据页的权限则相反)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-08-04
    • 1970-01-01
    • 2013-02-16
    • 1970-01-01
    • 2020-02-09
    • 2021-03-21
    • 1970-01-01
    相关资源
    最近更新 更多