Многофайловые проекты С++
Работал с многофайловыми проектами только в среде разработки. Сейчас работаю с g++ из GCC.
Сталкиваюсь с тем, что в примерах по С++ создают в среде разработки 2 файла: name.h и name.cpp. Затем в name.cpp подключают name.h — логично, ведь подтянутся заголовки.
Но в main.cpp подключают именно name.h, хотя тот содержит только заголовки. Это какой-то прикол среды разработки? Потому что по логике, чтобы в main.cpp была реализация функций, объявленных в name.h, нужно в файла name.h инклюдить name.cpp, а не наоборот.
И да, если создать в Linux 3 файла: name.h, name.cpp, main.cpp, затем в name.h создать прототипы, а в name.cpp — реализацию и подключить name.h, а только затем в main.cpp подключить name.h, то будет ругаться, что неопределенная ссылка на функцию из name.h.
Потому я и подумал, что это либо фича сред разработки — автоматом искать определение функций в <name>.cpp файлах, либо это фича компилятора.
Расскажите, будет интересно. А то я сейчас в .h файлы еще подключаю и .cpp файлы, иначе не работает просто...
Ответы (2 шт):
Неопределенная ссылка на функцию возникает потому, что Вы не указали где именно искать реализацию кода.
Случай include обрабатывает предпроцессор: в место где он указан вставляется весь .h файл, а потом все передается компилятору.
Дальше сами фунции нужно подгрузить - это может делаться на 2х следующих этапах.
На этапе компиляции cpp файл собирается в машинный код. Если указать несколько файлов, то машинный код собирается для каждого файла и объединяется в один выходной файл, который пока ещё не может запускаться, но имеет все функции из всех ваших файлов.
В Вашем примере можно просто:
g++ name.cpp main.cpp ....
Хотя можно и каждый файл компилировать отдельно, а потом собрать на третьем этапе.
IDE и проекты ориентированны на среду сборки, которая настраивается для сборки всех файлов.
На этапе линковки машинные коды соединяются в блоки структуры PE (Windows) или ELF (Linux) исполняемого файла. Тут недостающие функции можно загрузить из библиотек. Например Вы оформили name.h, name.cpp библиотекой и через опции -L и -l линковщик узнает что их можно найти в библиотеке.
П.С. пропустил этап. Между проепроцессингом и линковкой есть ещё ассемблер.
Итого вызов g++ без параметров на самом деле вызывает 4 программы компилятора
cpp — препроцессор - подстановка инклудов
as — ассемблер
g++ — сам компилятор - нужно вызвать для каждого .cpp файла (по одному или пачкой) чтоб все заработало
ld — линкер - упаковывает машинные коды в экзешник, указывает в каких дллках искать то что не хватает.
ППС. Вот подробная статья, вроде хорошо написанно.
Сборка исполняемого файла из исходного кода происходит в два этапа:
- Компиляция (трансляция, в том числе препроцессирование, ассемблирование и собственно компиляция) — создаёт объектные файлы (
*.o) из файлов реализации*.cpp - Линковка (компоновка) — связывает несколько объектных файлов в один исполняемый
При этом каждый модуль трансляции (читай файл *.cpp) компилируется отдельно. На применение такого подхода есть несколько причин, в частности это позволяет:
- сократить требование к ресурсам (в частности к памяти) необходимым для сборки
- не пересобирать всё заново при изменении только одного файла.
- комбинировать модули, написанные на разных языках
В классическом варианте сборка программы из нескольких файлов выглядит как-то так:
c++ -o main.o -c main.cpp
c++ -o name.o -c name.cpp
c++ -o prog main.o name.o # здесь фактически вызывается команда ld
Обычно все эти команды запускаются не в ручную, а с помощью сторонних утилит на подобии make и надстройками над ними вроде cmake или autotools.
Современные (выпущенные, хотя бы, в этом веке) компиляторы также позволяют выполнить это всё в одно действие:
g++ -o prog main.cpp name.cpp
А то я сейчас в .h файлы еще подключаю и .cpp файлы, иначе не работает просто...
Так делать, конечно, не стоит — это лишает смысла всю систему раздельной компиляции.
Потому я и подумал, что это либо фича сред разработки - автоматом искать определение функций в .cpp файлах, либо это фича компилятора.
На самом деле, всё что делают среды разработки при сборке — это автоматизируют работу с утилитой make или её аналогами/надстройками.