Я прочитал несколько постов и пришел к выводу, что extern сообщает компилятору: «Эта функция существует, но код для нее где-то еще. Не паникуйте». Но как компоновщик узнает, где определена функция.
Мое дело:-
Я работаю над Keil Uvision 4. Существует заголовочный файл grlib.h, а основная функция находится в grlib_demo.c (он включает grlib.h). Теперь есть функция GrCircleDraw (), которая определена в Circle.c и вызвана в grlib_demo.c, также есть оператор
extern void GrCircleDraw (все аргументы);
в grlib.h. Мой запрос заключается в том, как компоновщик знает, где находится определение GrCircleDraw (), поскольку Circle.c не включен в grlib.h и grlib_demo.c
Примечание: — файлы grlib.h и Circle.c находятся в одной папке. Код выполняется успешно.
Простой ответ заключается в том, что «компилятору не нужно знать, но компоновщик должен его найти». Через несколько .o
файлы, или через библиотеки, компоновщик должен быть в состоянии найти единственное определение GrCircleDraw
функция.
Компилятор помещает только имя extern
функция в .obj
файл. Компилятору не нужно знать больше об этом.
Когда вы начинаете создавать ссылки, вы, как разработчик, обязаны предоставить компоновщику все необходимые объектные и библиотечные файлы. Линкер организует все эти функции в двоичный файл. Если вы не указали правильные библиотеки или .obj
файлы, связь просто не удастся с unresolved blah-blah
,
Библиотеки по умолчанию обычно включаются неявно. Это усложняет вещи и создает иллюзии. Вы всегда можете указать, что вы не хотите никаких неявных библиотек и включать все явно. К сожалению, каждая система делает это по-своему.
Когда вы компилируете файл .o в Формат ELF, у вас есть много вещей на .o
файл, такой как:
.text
раздел, содержащий код;.data
, .rodata
, .rss
разделы, содержащие глобальные переменные;.symtab
содержащий список символов (функций, глобальных переменных и других) в .o
(и их расположение в файле), а также символы, используемые .o
файл;.rela.text
Это список перемещений — это те изменения, которые редактор ссылок (и / или динамический компоновщик) должен будет сделать, чтобы связать различные части вашей программы вместе.Давайте скомпилируем простой C-файл:
extern void GrCircleDraw(int x);
int foo()
{
GrCircleDraw(42);
return 3;
}
int bla()
{
return 2;
}
с:
gcc -o test.o test.c -c
(Я использую встроенный компилятор моей системы, но он будет работать точно так же при кросс-компиляции в ARM).
Вы можете посмотреть содержимое вашего .o файла с помощью:
readelf -a test.o
В таблице символов вы найдете:
Таблица символов «.symtab» содержит 10 записей: Num: Значение Размер Тип Bind Vis Ndx Имя 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND [...] 8: 0000000000000000 21 FUNC GLOBAL DEFAULT 1 foo 9: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND GrCircleDraw 10: 0000000000000015 11 FUNC GLOBAL DEFAULT 1 бла
Есть один символ для нашего foo
функции и один для bla
, Поле значения дает их местоположение в пределах .text
раздел.
Существует один символ для используемого символа GrCircleDraw
: это не определено, потому что эта функция не определена в этом .o
файл, но остается найти в другом месте.
В таблице перемещения для .text
раздел (.rela.text
) ты находишь:
Раздел перемещения '.rela.text' со смещением 0x260 содержит 1 запись: Информация о смещении Тип Sym. Значение Sym. Имя + Добавление 00000000000a 000900000002 R_X86_64_PC32 0000000000000000 GrCircleDraw - 4
Этот адрес находится в пределах foo
: редактор ссылок пропустит инструкцию по этому адресу с адресом GrCircleDraw
функция.
Теперь давайте скомпилируем реализацию GrCircleDraw
:
void GrCircleDraw(int x)
{
}
Давайте посмотрим на его таблицу символов:
Таблица символов «.symtab» содержит 9 записей: Num: Значение Размер Тип Bind Vis Ndx Имя [...] 8: 0000000000000000 9 FUNC GLOBAL DEFAULT 1 GrCircleDraw
Имеется запись для GrCircleDraw
определяя его местоположение в пределах его .text
раздел.
Поэтому, когда редактор ссылок объединяет оба файла, он становится известным:
.o
файл и их местоположение;Связывание обычно происходит следующим образом: командная строка повторяется, и каждый аргумент
В конце, каждая ссылка должна быть выполнена, чтобы успешно связать. Порядок строк, указанных в командной строке компоновщика, важен.