Компилятор этого не делает, компоновщик делает.
Хотя компилятор работает с одним исходным файлом за раз, когда вызывается компоновщик, ему передаются имена всех объектных файлов, сгенерированных компилятором, а также любых библиотек, с которыми пользователь хочет связать. Следовательно, компоновщик обладает полным знанием набора файлов, которые потенциально могут содержать определение, и ему нужно только просматривать таблицы символов этих объектных файлов. За этим не нужно ничего искать.
Например, скажем, у вас есть 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, и завершает ссылку.
С библиотеками это немного сложнее, и порядок их появления в командной строке может иметь значение в зависимости от вашего компоновщика, но в целом это тот же принцип.