将整个标准库链接到每个 C/C++ 文件 space 是否有效?
Is linking the entire standard library to every C/C++ file space efficient?
让整个标准库或任何其他库已经编译并准备好 linked 以形成可执行文件是一个很好的功能,它使编译速度更快,但据我所知,整个即使只使用了其中的几个函数,库也是 linked。
因此,例如在我的机器上,以下代码编译为目标代码时为 1.6 kB,但当我 link 将其编译为标准库时,它几乎变为 17 kB。
#include <stdio.h>
int main(void)
{
printf("Hello world\n");
}
是否有任何其他方法可以仅重新编译标准库(或任何其他库)中必要的部分以使程序更 space 高效?
抱歉,如果这个问题已经被问过了,我用谷歌搜索了它,但找不到任何答案。
如果我们转储生成的 ELF 可执行文件的内容,我们将看到 二进制文件中没有嵌入 C 运行时库的任何部分,因为 GLIBC 库链接在 动态地(例如来自libc.so.6
)。
$ gcc -Os -s a.c
$ ls -la a.out
-rwxrwxrwx 1 user user 14408 Feb 18 12:56 a.out
$ ldd a.out
linux-vdso.so.1 (0x00007ffd157f8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007feb0a33f000)
/lib64/ld-linux-x86-64.so.2 (0x00007feb0a518000)
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 puts
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
我们还注意到 GCC 将没有占位符的 printf
优化为 puts
(文件大小无关紧要)。
要查看 ELF 的“内部”,我们可以转储其部分:
$ readelf -SW a.out
There are 28 section headers, starting at offset 0x3148:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 0002a8 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000000000002c4 0002c4 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 00000000000002e4 0002e4 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 000308 000024 00 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000330 000330 0000a8 18 A 6 1 8
[ 6] .dynstr STRTAB 00000000000003d8 0003d8 000082 00 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000045a 00045a 00000e 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000468 000468 000020 00 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000488 000488 0000c0 18 A 5 0 8
[10] .rela.plt RELA 0000000000000548 000548 000018 18 AI 5 23 8
[11] .init PROGBITS 0000000000001000 001000 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000001020 001020 000020 10 AX 0 0 16
[13] .plt.got PROGBITS 0000000000001040 001040 000008 08 AX 0 0 8
[14] .text PROGBITS 0000000000001050 001050 000171 00 AX 0 0 16
[15] .fini PROGBITS 00000000000011c4 0011c4 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000002000 002000 000010 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000002010 002010 00003c 00 A 0 0 4
[18] .eh_frame PROGBITS 0000000000002050 002050 000100 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000003de8 002de8 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000003df0 002df0 000008 08 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000003df8 002df8 0001e0 10 WA 6 0 8
[22] .got PROGBITS 0000000000003fd8 002fd8 000028 08 WA 0 0 8
[23] .got.plt PROGBITS 0000000000004000 003000 000020 08 WA 0 0 8
[24] .data PROGBITS 0000000000004020 003020 000010 00 WA 0 0 8
[25] .bss NOBITS 0000000000004030 003030 000008 00 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 003030 00001c 01 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00304c 0000f7 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
由于 PIC、relro、eh_frame 和 GNU 链接器生成的其他部分,目前 ~14 kB 几乎是最小的 ELF 可执行文件大小。
您可以通过关闭 relro(这会稍微降低安全性)来稍微减小大小。
$ gcc -Os -s -z norelro -Wl,--gc-sections a.c
$ $ ls -la a.out
-rwxrwxrwx 1 user user 10960 Feb 18 13:06 a.out
让整个标准库或任何其他库已经编译并准备好 linked 以形成可执行文件是一个很好的功能,它使编译速度更快,但据我所知,整个即使只使用了其中的几个函数,库也是 linked。
因此,例如在我的机器上,以下代码编译为目标代码时为 1.6 kB,但当我 link 将其编译为标准库时,它几乎变为 17 kB。
#include <stdio.h>
int main(void)
{
printf("Hello world\n");
}
是否有任何其他方法可以仅重新编译标准库(或任何其他库)中必要的部分以使程序更 space 高效?
抱歉,如果这个问题已经被问过了,我用谷歌搜索了它,但找不到任何答案。
如果我们转储生成的 ELF 可执行文件的内容,我们将看到 二进制文件中没有嵌入 C 运行时库的任何部分,因为 GLIBC 库链接在 动态地(例如来自libc.so.6
)。
$ gcc -Os -s a.c
$ ls -la a.out
-rwxrwxrwx 1 user user 14408 Feb 18 12:56 a.out
$ ldd a.out
linux-vdso.so.1 (0x00007ffd157f8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007feb0a33f000)
/lib64/ld-linux-x86-64.so.2 (0x00007feb0a518000)
$ objdump -T a.out
a.out: file format elf64-x86-64
DYNAMIC SYMBOL TABLE:
0000000000000000 w D *UND* 0000000000000000 _ITM_deregisterTMCloneTable
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 puts
0000000000000000 DF *UND* 0000000000000000 GLIBC_2.2.5 __libc_start_main
0000000000000000 w D *UND* 0000000000000000 __gmon_start__
0000000000000000 w D *UND* 0000000000000000 _ITM_registerTMCloneTable
0000000000000000 w DF *UND* 0000000000000000 GLIBC_2.2.5 __cxa_finalize
我们还注意到 GCC 将没有占位符的 printf
优化为 puts
(文件大小无关紧要)。
要查看 ELF 的“内部”,我们可以转储其部分:
$ readelf -SW a.out
There are 28 section headers, starting at offset 0x3148:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 0002a8 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000000000002c4 0002c4 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 00000000000002e4 0002e4 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 000308 000024 00 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000000330 000330 0000a8 18 A 6 1 8
[ 6] .dynstr STRTAB 00000000000003d8 0003d8 000082 00 A 0 0 1
[ 7] .gnu.version VERSYM 000000000000045a 00045a 00000e 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000000468 000468 000020 00 A 6 1 8
[ 9] .rela.dyn RELA 0000000000000488 000488 0000c0 18 A 5 0 8
[10] .rela.plt RELA 0000000000000548 000548 000018 18 AI 5 23 8
[11] .init PROGBITS 0000000000001000 001000 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000001020 001020 000020 10 AX 0 0 16
[13] .plt.got PROGBITS 0000000000001040 001040 000008 08 AX 0 0 8
[14] .text PROGBITS 0000000000001050 001050 000171 00 AX 0 0 16
[15] .fini PROGBITS 00000000000011c4 0011c4 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000002000 002000 000010 00 A 0 0 4
[17] .eh_frame_hdr PROGBITS 0000000000002010 002010 00003c 00 A 0 0 4
[18] .eh_frame PROGBITS 0000000000002050 002050 000100 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000003de8 002de8 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000003df0 002df0 000008 08 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000003df8 002df8 0001e0 10 WA 6 0 8
[22] .got PROGBITS 0000000000003fd8 002fd8 000028 08 WA 0 0 8
[23] .got.plt PROGBITS 0000000000004000 003000 000020 08 WA 0 0 8
[24] .data PROGBITS 0000000000004020 003020 000010 00 WA 0 0 8
[25] .bss NOBITS 0000000000004030 003030 000008 00 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 003030 00001c 01 MS 0 0 1
[27] .shstrtab STRTAB 0000000000000000 00304c 0000f7 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
由于 PIC、relro、eh_frame 和 GNU 链接器生成的其他部分,目前 ~14 kB 几乎是最小的 ELF 可执行文件大小。
您可以通过关闭 relro(这会稍微降低安全性)来稍微减小大小。
$ gcc -Os -s -z norelro -Wl,--gc-sections a.c
$ $ ls -la a.out
-rwxrwxrwx 1 user user 10960 Feb 18 13:06 a.out