Как компилятор C/C++ находит определения прототипов в заголовочных файлах?

Когда я объявляю функцию в заголовочном файле и помещаю определение той функции в некотором другом файле, как компилятор/компоновщик находит определение? Это систематически ищет каждый файл в своем пути для него или является там более изящным решением? Это прослушивало меня в течение прошедших нескольких дней, и я не мог найти объяснение его.

21
задан Tyler McHenry 30 July 2010 в 01:16
поделиться

3 ответа

Компилятор не делает этого, это делает компоновщик.

В то время как компилятор работает с одним исходным файлом за раз, при вызове компоновщика ему передаются имена всех объектных файлов, созданных компилятором, а также любых библиотек, которые пользователь хочет подключить. Таким образом, компоновщик имеет полное представление о наборе файлов, которые потенциально могут содержать определение, и ему нужно только заглянуть в таблицы символов этих объектных файлов. Ему не нужно делать никакого поиска сверх этого.

Например, допустим, у вас есть foo.h и foo.c, определяющие и реализующие функцию foo(), и bar.h и bar.c, определяющие и реализующие bar(). Скажем, bar вызывает foo, так что bar.c включает foo.h. В этой компиляции есть три шага:

gcc -c foo.c
gcc -c bar.c
gcc foo.o bar.o -o program

Первая строка компилирует foo.c, создавая foo.o. Вторая компилирует bar.c, создавая bar.o. В этот момент в объектном файле bar.o foo является внешним символом. Третья строка вызывает компоновщик, который соединяет foo.o и bar.o в исполняемый файл под названием "program". Когда компоновщик обрабатывает bar.o, он видит неразрешенный внешний символ foo, поэтому он просматривает таблицу символов всех других компонуемых объектных файлов (в данном случае только foo.o) и находит foo в foo.o, и завершает компоновку.

С библиотеками это немного сложнее, и порядок их появления в командной строке может иметь значение в зависимости от вашего компоновщика, но в целом принцип тот же.

25
ответ дан 29 November 2019 в 20:43
поделиться

Предположим, у вас есть файл foo.cpp с #include foo.h и, возможно, другими включениями. Заголовки, конечно, могут иметь свои собственные # include-ы.

Препроцессор начнет с foo.cpp, проанализирует #includes и прочитает содержимое заголовка. Результатом будет текст из заголовочных файлов и "сплющенный" foo.cpp. Затем компилятор обработает этот текст. Если переменная / функция / и т. Д. Должны иметь был объявлен где-то в заголовке, не найден, компилятор сообщит об ошибке.

Суть в том, что компилятор должен видеть все свои объявления как результат .cpp и заголовков.

0
ответ дан 29 November 2019 в 20:43
поделиться

Когда вы компилируете файл .cpp, компилятор выводит две таблицы в файл .obj: список символов, которые, как он ожидает, будут определены извне, а также список символов, определенных в данном конкретном модуле.

Компоновщик берет все файлы .obj, которые были выведены компилятором, и затем (как следует из названия) соединяет их вместе. Поэтому для каждого модуля он просматривает список символов, которые помечены как "определенные извне", и просматривает все другие модули, которые ему были предоставлены для этих символов.

Таким образом, он "ищет" только в тех модулях, в которых вы сказали ему искать.

Если он не может найти символ ни в одном из других модулей, то возникает ошибка "неопределенная ссылка".

14
ответ дан 29 November 2019 в 20:43
поделиться