О компоновке программ
Компоновка: объектный файл → исполняемый файл (к объектному файлу добавлена исполняющая подсистема Си и другие подпрограммы, непосредственно либо в виде указания библиотеки, откуда эти подпрограммы может загрузить операционная система) Пример: файл simple.c
Здесь мы определяем переменную var и функцию main, а функции printf и scanf становятся доступны только в процессе компоновки. С помощью ключа -c остановим процесс трансляции на объектном файле (файл этот будет называться simple.o):
$ cc -std=c89 simple.c -c -o simple.o $ hexdump -C simple.o <содержимое объектного файла в шестнадцатеричном виде> ... $ nm simple.o 0000000000000000 T main U printf U scanf 0000000000000004 C var $ ls -l simple.* -rw-r--r-- 1 george george 99 янв 29 10:43 simple.c -rw-r--r-- 1 george george 1680 янв 29 10:47 simple.o
Утилита hexdump покажет нам внутренности simple.o (среди трёхкилобайтного шестнадцатеричного месива можно отыскать все четыре названия). Утилита nm — выдаст информацию о том, какие идентификаторы используются в программе, при этом те, что не определены, помечены как U (undefined). При компиляции использовался старый стандарт c89, чтобы функции назывались более наглядно.
Компоновка:
$ cc -static simple.o -o simple.static $ nm simple.static <множество имён, среди которых есть уже `main, printf, scanf и var> $ nm simple.static | egrep ' (main|var|printf|scanf)$' 000000000040096e T main 0000000000407440 T printf 0000000000407570 T scanf 00000000006b2d58 B var $ ls -l simple.* -rw-r--r-- 1 george george 99 янв 29 10:43 simple.c -rw-r--r-- 1 george george 1680 янв 29 10:47 simple.o -rwxr-xr-x 1 george george 3486600 янв 29 10:50 simple.static
Статическая компоновка добавляет в объектный файл всю исполняющую систему Си и всю отладочную информацию к ней, так что получается программа в 1000 раз больше. С помощью egrep из отладочной информации добудем сведения о четырёх именах. Они уже определены, и даже адреса есть.
Отладочную информацию можно удалить:
$ strip simple.static -o simple.static.stripped $ ls -l simple.* -rw-r--r-- 1 george george 99 янв 29 10:43 simple.c -rw-r--r-- 1 george george 1680 янв 29 10:47 simple.o -rwxr-xr-x 1 george george 3486600 янв 29 10:50 simple.static -rwxr-xr-x 1 george george 730112 янв 29 10:56 simple.static.stripped
Размер раз в 5 меньше.
Вместо статической компоновки обычно используется динамическая, при которой нужные функции в исполняемый файл не записываются, а вместо этого там лежит информация, из какой библиотеки их брать при запуске.
$ cc simple.o -o simple.dynamic $ nm simple.dynamic | egrep ' (main|var|printf|scanf)' 0000000000400566 T main U printf@@GLIBC_2.2.5 U scanf@@GLIBC_2.2.5 0000000000601038 B var $ ldd simple.dynamic linux-vdso.so.1 (0x00007ffe939de000) libc.so.6 => /lib64/libc.so.6 (0x00007fefe7726000) /lib64/ld-linux-x86-64.so.2 (0x000055e6bc426000)
В этой программе используется единственная библиотека — libc — исполняющая система Си. В ней-то и находятся printf и scanf. Функции будут подгружены системой из библиотеки во время запуска программы.
$ objdump -T /lib64/libc.so.6 | egrep ' (main|var|printf|scanf)$' 000000000004eb00 g DF .text 00000000000000a1 GLIBC_2.2.5 printf 0000000000062fa0 g DF .text 00000000000000a3 GLIBC_2.2.5 scanf $ strip simple.dynamic -o simple.dynamic.stripped $ ls -l итого 4156 -rw-r--r-- 1 george george 99 янв 29 10:43 simple.c -rwxr-xr-x 1 george george 12528 янв 29 10:57 simple.dynamic -rwxr-xr-x 1 george george 6312 янв 29 11:10 simple.dynamic.stripped -rw-r--r-- 1 george george 1680 янв 29 10:47 simple.o -rwxr-xr-x 1 george george 3486600 янв 29 10:50 simple.static -rwxr-xr-x 1 george george 730112 янв 29 10:56 simple.static.stripped
По этой причине файлы получаются меньше (и с отладочной информации, и без неё). Утилита objdump предназначена для исследования динамических библиотек, с ключом -T она выдаёт список видимых в ней символов (преимущественно функций).
Функции printf() и scanf() находятся в стандартной библиотеки, а математические функции — в библиотеке libm. Поэтому программу, использующую такие функции надо компоновать с этой библиотекой. Вот программа, вычисляющая квадратный корень.
При попытке скомпилировать этот код «просто так» компоновщик выводит ошибку вида «а где же функция?» (обратите внимание на имя временного объектного файла):
$ cc math.c /tmp/.private/george/ccb5je3d.o: In function `main': math.c:(.text+0x28): undefined reference to `sqrt' collect2: error: ld returned 1 exit status
Для компоновки с соответствующей библиотекой компоновщику надо передать ключ -l<библиотека>. В нашем случае библиотека называется libm, так что ключ выходит -lm:
$ cc -std=c89 math.c -c -o math.o $ nm math.o | egrep ' (main|var|printf|scanf|sqrt)' 0000000000000000 T main U printf U scanf U sqrt $ cc math.o -lm -o math $ nm math | egrep ' (main|var|printf|scanf|sqrt)' 0000000000400676 T main U printf@@GLIBC_2.2.5 U scanf@@GLIBC_2.2.5 U sqrt@@GLIBC_2.2.5 $ ldd math linux-vdso.so.1 (0x00007ffcd610f000) libm.so.6 => /lib64/libm.so.6 (0x00007f2839862000) libc.so.6 => /lib64/libc.so.6 (0x00007f28394bf000) /lib64/ld-linux-x86-64.so.2 (0x000055941d370000) $ objdump -T /lib64/libc.so.6 | egrep ' (main|var|printf|scanf|sqrt)$' 000000000004eb00 g DF .text 00000000000000a1 GLIBC_2.2.5 printf 0000000000062fa0 g DF .text 00000000000000a3 GLIBC_2.2.5 scanf $ objdump -T /lib64/libm.so.6 | egrep ' (main|var|printf|scanf|sqrt)$' 0000000000021310 w DF .text 000000000000002a GLIBC_2.2.5 sqrt
UPD 2023-09-26:
Компоновка производится специальной утилитой ld (компоновщиком). Ей на вход подаётся преизрядно параметров, которые можно увидеть, если запустить gcc -v:
$ cc simple.o -v -o simple 2>&1 | grep collect2 /usr/lib64/gcc/x86_64-alt-linux/13/collect2 -plugin /usr/lib64/gcc/x86_64-alt-linux/13/liblto_plugin.so -plugin-opt=/usr/lib64/gcc/x86_64-alt-linux/13/lto-wrapper -plugin-opt=-fresolution=/tmp/.private/george/ccMCkMzN.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --no-add-needed --eh-frame-hdr --hash-style=gnu --as-needed -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -o simple /usr/lib64/gcc/x86_64-alt-linux/13/../../../../lib64/Scrt1.o /usr/lib64/gcc/x86_64-alt-linux/13/../../../../lib64/crti.o /usr/lib64/gcc/x86_64-alt-linux/13/crtbeginS.o -L/usr/lib64/gcc/x86_64-alt-linux/13 -L/usr/lib64/gcc/x86_64-alt-linux/13/../../../../lib64 -L/lib/../lib64 -L/usr/lib/../lib64 -L/usr/lib64/gcc/x86_64-alt-linux/13/../../.. simple.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib64/gcc/x86_64-alt-linux/13/crtendS.o /usr/lib64/gcc/x86_64-alt-linux/13/../../../../lib64/crtn.o
collect2 — это враппер для ld
- Нас интересует:
Интерпретатор исполняемых файлов — ld-linux-x86-64.so.2
Дополнительные библиотеки — например, стандартные функции Си, -lc (-lgcc, видимо, не нужно)
Дополнительные .о файлы: исполняющая система Си, код для начала программы и для конца программы
TODO формат получившейся команды ld
Что-то ещё?