О компоновке программ

Компоновка: объектный файл → исполняемый файл (к объектному файлу добавлена исполняющая подсистема Си и другие подпрограммы, непосредственно либо в виде указания библиотеки, откуда эти подпрограммы может загрузить операционная система) Пример: файл simple.c

   1 #include <stdio.h>
   2 
   3 int var;
   4 
   5 void main() {
   6     scanf("%d", &var);
   7     printf("%d\n", var);
   8 }

Здесь мы определяем переменную 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. Поэтому программу, использующую такие функции надо компоновать с этой библиотекой. Вот программа, вычисляющая квадратный корень.

   1 #include <stdio.h>
   2 #include <math.h>
   3 
   4 int main() {
   5   float f;
   6 
   7   scanf("%f", &f);
   8   printf("%f\n", sqrt(f));
   9 }

При попытке скомпилировать этот код «просто так» компоновщик выводит ошибку вида «а где же функция?» (обратите внимание на имя временного объектного файла):

$ 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

TODO формат получившейся команды ld

Что-то ещё?

FrBrGeorge/LinkingC (последним исправлял пользователь FrBrGeorge 2023-09-26 15:13:09)