Когда вы запускаете g++ -I. -l. ABC.cpp
, вы просите компилятор создать исполняемый файл из ABC.cpp
. Но код в этом файле отвечает на функцию, определенную в XYZ.cpp
, поэтому исполняемый файл не может быть создан из-за этой отсутствующей функции.
У вас есть два варианта (в зависимости от того, что вы хотите сделать). Либо вы даете компилятору все исходные файлы одновременно, чтобы он имел все определения, например
g++ -I. -l. ABC.cpp XYZ.cpp
или вы используете опцию -c
для компиляции в ABC.cpp с объектным кодом (.obj в Windows, .o в Linux), который можно связать позже, например,
g++ -I. -l. -c ABC.cpp
Который выдаст ABC.o
, который позже можно связать с XYZ.o
для создания исполняемого файла.
Редактировать: В чем разница между включением и включением # 1018 *
Для полного понимания этого требуется понимание того, что происходит при компиляции программы на C ++, чего, к сожалению, даже многие люди, считающие себя программистами на C ++, нет. На высоком уровне компиляция программы на C ++ проходит три этапа: предварительная обработка, компиляция и компоновка.
1024 * Препроцессирование *
Каждая строка, начинающаяся с #
, представляет собой директиву препроцессора , которая оценивается на этапе предварительной обработки. Директива #include
является буквально копированием и вставкой. Если вы напишите #include "XYZ.h"
, препроцессор заменит эту строку всем содержимым XYZ.h
(включая рекурсивные вычисления #include
в XYZ.h
).
Цель включения - сделать объявления видимыми. Чтобы использовать функцию GetOneGaussianByBoxMuller
, компилятор должен знать, что GetOneGaussianByBoxMuller
является функцией, и знать, какие (если они есть) аргументы, которые он принимает, и какое значение он возвращает, компилятору необходимо увидеть объявление для него. , Объявления помещаются в заголовочные файлы, а заголовочные файлы включены, чтобы сделать объявления видимыми для компилятора до момента использования.
Компиляция
Эта часть запускает компилятор и превращает ваш исходный код в машинный код. Обратите внимание, что машинный код - это не то же самое, что исполняемый код. Исполняемый файл требует дополнительной информации о том, как загрузить машинный код и данные в память, и как при необходимости использовать внешние динамические библиотеки. Это не сделано здесь. Это та часть, где ваш код переходит с C ++ на необработанные машинные инструкции.
В отличие от Java, Python и некоторых других языков, C ++ не имеет понятия «модуль». Вместо этого C ++ работает в терминах единиц перевода . Почти во всех случаях единица перевода соответствует одному файлу исходного кода (без заголовка), например ABC.cpp
или XYZ.cpp
. Каждый модуль перевода компилируется независимо (независимо от того, запускаете ли вы для них отдельные команды -c
или передаете их компилятору сразу).
Когда исходный файл компилируется, препроцессор запускается первым и выполняет #include
вставку копии, а также макросы и другие действия, которые выполняет препроцессор. В результате получается один длинный поток кода C ++, состоящий из содержимого исходного файла и всего включенного в него (и всего, что включено в него, и т. Д.). Этот длинный поток кода является единицей перевода.
Когда модуль трансляции компилируется, каждая функция и каждая используемая переменная должны быть объявлены . Компилятор не позволит вам вызвать функцию, для которой нет объявления, или использовать глобальную переменную, для которой нет объявления, потому что тогда он не будет знать типы, параметры, возвращаемые значения и т. Д., Участвующие и не сможет генерировать разумный код. Вот почему вам нужны заголовки - имейте в виду, что на данный момент компилятор даже не знает о существовании каких-либо других исходных файлов; он рассматривает только этот поток кода, созданный обработкой директив #include
.
В машинном коде, созданном компилятором, нет таких вещей, как имена переменных или имена функций.Все должно стать адресом памяти.Каждая глобальная переменная должна быть преобразована в адрес памяти, где она хранится, и каждая функция должна иметь адрес памяти, к которому переходит поток выполнения при ее вызове.Для вещей, которые определены (т. Е. Для функций, реализованных ) в модуле перевода, компилятор может назначить адрес.Для вещей, которые только объявлены (обычно в результате включенных заголовков) и не определены, компилятор на данный момент не знает, каким должен быть адрес памяти.Эти функции и глобальные переменные, для которых компилятор имеет только объявление, но не определение / реализацию, называются внешними символами , и предполагается, что они существуют в другой единице перевода.На данный момент их адреса памяти представлены заполнителями.
Например, при компиляции единицы перевода, соответствующей ABC.cpp
, он имеет определение (реализацию) ABC
, поэтому он может назначать адресфункция ABC
и где бы в этом блоке перевода не вызывался ABC
, она может создать инструкцию перехода по этому адресу.С другой стороны, хотя его объявление является видимым, GetOneGaussianByBoxMuller
не реализовано в этом модуле перевода, поэтому его адрес должен быть представлен с помощью заполнителя.
Результатом компиляции модуля перевода является объектный файл (с суффиксом .o
в Linux).
Linking
Одно из основных заданий компоновщика - resol внешние символы.То есть компоновщик просматривает набор объектных файлов, видит их внешние символы и затем пытается выяснить, какой адрес памяти должен быть им назначен, заменяя заполнитель.
В вашем случае функцияGetOneGaussianByBoxMuller
- это , определенный в единице перевода, соответствующей XYZ.cpp
, поэтому внутри XYZ.o
ему был назначен определенный адрес памяти.В единице перевода, соответствующей ABC.cpp
, было только объявлено , поэтому внутри ABC.o
это только заполнитель (внешний символ).Компоновщик, если ему даны ABC.o
и XYZ.o
, увидит, что ABC.o
нужен адрес, заполненный для GetOneGaussianByBoxMuller
, найдите этот адрес в XYZ.o
и замените заполнитель в ABC.o
на него.Адреса для внешних символов также можно найти в библиотеках.
Если компоновщик не может найти адрес для GetOneGaussianByBoxMuller
(как это происходит в вашем примере, когда он работает только на ABC.o
, из-за того, что не передал XYZ.cpp
компилятору),он сообщит о неразрешенной внешней ошибке символа, также описанной как неопределенная ссылка .
Наконец, после того, как компилятор разрешил все внешние символы, он объединяет все объект, свободный от заполнителей.код, добавляет всю информацию о загрузке, которая нужна операционной системе, и создает исполняемый файл.Тада!
Обратите внимание, что при всем этом имена файлов не имеют значения, один бит.Это соглашение о том, что XYZ.h
должно содержать объявления для вещей, которые определены в XYZ.cpp
, и это хорошо для поддерживаемого кода, чтобы организовать вещи таким образом, но компилятору и компоновщику все равно,это правда или нет.Компоновщик просматривает все предоставленные ему объектные файлы и только предоставленные им объектные файлы, чтобы попытаться разрешить символ.Он не знает и не заботится, в каком заголовке содержалось объявление символа, и он не будет пытаться автоматически извлекать другие объектные файлы или компилировать другие исходные файлы для разрешения отсутствующего символа.
... wowэто было долго.