0

Having the entire standard library or any other library already compiled and ready to be linked to form an executable is a nice feature and it makes the compilation faster, but as far as I know, the entire library is linked even if only a few functions in it are used.

So for instance on my machine, the following code is 1.6 kB when compiled to object code but it becomes almost 17 kB when I link it to the standard library.

#include <stdio.h>

int main(void)
{
    printf("Hello world\n");
}

Is there any other way to recompile only the parts of the standard library (or any other library) that are necessary in order to make the program more space-efficient?

Sorry if the question is already asked, I googled it but couldn't find any answers to it.

Amirreza A.
  • 736
  • 4
  • 10
  • Maybe a dup of https://stackoverflow.com/questions/18790966/strip-unused-runtime-functions-which-bloat-executable-gcc – Martheen Feb 18 '21 at 10:01
  • 3
    The entire standard library is *much* more than 17 kB. – molbdnilo Feb 18 '21 at 10:18
  • Compile with optimizations on and use `puts` instead of `printf`. It's a much simpler function. That may reduce the size of the linked code. – paddy Feb 18 '21 at 10:36
  • 1
    No, the whole standard library is not typically linked in. The standard doesn't actually specify what is "linked in", and different implementations (i.e. toolchains) behave differently. The reason your code is somewhat larger than its source is that `printf()` does a lot of things (parsing the format string, conditionally doing things based on the content of that string, etc) and that involves a fair number of instructions. Even in cases like this one, where the format string doesn't include any format specifiers (fields beginning with a `%` character). – Peter Feb 18 '21 at 11:08
  • The reason it's called a **library** is that the linker pulls in what it needs, just like when someone goes to a library and check out the books they want and not all of the books in the library. – Pete Becker Feb 18 '21 at 14:32

1 Answers1

2

If we dump the contents of the generated ELF executable we will see no parts of the C runtime library embedded in the binary, because the GLIBC library is linked in dynamically (e.g. from 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

We notice also that GCC optimized a printf with no placeholders into a puts (for the file size it doesn't matter).

To see "inside" the ELF we can dump its sections:

$ 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)

So ~14 kB is pretty much the minimal ELF executable size these days due to PIC, relro, eh_frame and other sections generated by the GNU linker.

You can reduce the size somewhat by turning off relro (which reduces the security a little).

$ 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
rustyx
  • 80,671
  • 25
  • 200
  • 267