Подтвердить что ты не робот

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

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

4b9b3361

Ответ 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 и заголовков.