Когда я объявляю функцию в заголовочном файле и ставил определение этой функции в какой-то другой файл, каким образом компилятор/компоновщик находит определение? Систематически ли он ищет каждый файл на своем пути, или есть более элегантное решение? Последние несколько дней меня беспокоили, и я не смог найти объяснения.
Как компилятор C/С++ находит определения прототипов в файлах заголовков?
Ответ 1
Компилятор не делает этого, компоновщик делает.
Пока компилятор работает в одном исходном файле за раз, когда вызывается компоновщик, ему передаются имена всех файлов объектов, сгенерированных компилятором, а также любые библиотеки, к которым пользователь хочет связать. Поэтому компоновщик имеет полное представление о наборе файлов, которые потенциально могут содержать определение, и ему нужно только посмотреть в таблицах символов этих объектных файлов. Для этого не нужно делать никаких поисков.
Например, скажем, что у вас есть 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 и завершает ссылку.
С библиотеками это немного сложнее, и порядок, который они отображаются в командной строке, может иметь значение в зависимости от вашего компоновщика, но в целом тот же принцип.
Ответ 2
Когда вы компилируете файл .cpp, компилятор выводит две таблицы в файле .obj: список символов, которые он ожидает определить извне, а также список символов, которые определены в этом конкретном модуле.
Компонент принимает все файлы .obj, которые были выведены компилятором, а затем (как следует из названия) связывает их все вместе. Поэтому для каждого модуля он просматривает список символов, которые помечены как "определенные извне", и просматривает все остальные модули, которые были предоставлены для этих символов.
Таким образом, он только "ищет" модули, в которые вы сказали, что он ищет.
Если он не может найти символ в любом из других модулей, это когда вы получите ошибку "undefined reference".
Ответ 3
Предположим, что у вас есть foo.cpp С#include foo.h, а может быть и другие. Конечно, заголовки могут иметь свои собственные # include-s.
Препроцессор начнет с foo.cpp, проанализирует #includes и прочитает содержимое заголовка. Результатом будет текст из файлов заголовков и foo.cpp "flattened". Затем компилятор отработает этот текст. Если переменная/функция/etc должна иметь был объявлен где-то в заголовке не найден, компилятор сообщит об ошибке.
Основной момент заключается в том, что компилятор должен видеть все свои объявления в результате .cpp и заголовков.