Многофайловые проекты С++

Работал с многофайловыми проектами только в среде разработки. Сейчас работаю с 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 шт):

Автор решения: eri

Неопределенная ссылка на функцию возникает потому, что Вы не указали где именно искать реализацию кода.

Случай 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 — линкер - упаковывает машинные коды в экзешник, указывает в каких дллках искать то что не хватает.

ППС. Вот подробная статья, вроде хорошо написанно.

→ Ссылка
Автор решения: Fat-Zer

Сборка исполняемого файла из исходного кода происходит в два этапа:

  1. Компиляция (трансляция, в том числе препроцессирование, ассемблирование и собственно компиляция) — создаёт объектные файлы (*.o) из файлов реализации *.cpp
  2. Линковка (компоновка) — связывает несколько объектных файлов в один исполняемый

При этом каждый модуль трансляции (читай файл *.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 или её аналогами/надстройками.

→ Ссылка