Компиляция и компоновка
Программа на C++ часто состоит из нескольких .cpp-файлов. Компилятор переводит каждый файл отдельно и, читая один, не видит, что написано в соседнем. Но вызов функции компилируется, только если компилятор уже знает, что такая функция есть. Вот программа из двух файлов: main.cpp вызывает функцию triple, а её тело лежит в triple.cpp.
Файл main.cpp:
#include <iostream>
int triple(int x);
int main() {
std::cout << triple(14) << '\n'; // 42
}Файл triple.cpp:
int triple(int x) {
return x * 3;
}Компиляцию обоих файлов и их компоновку в одну программу вместе называют сборкой (build).
Объявление и определение
Строка int triple(int x); в main.cpp — это объявление (declaration). Оно повторяет первую строку функции: тип результата, имя и то, что функция принимает, а вместо тела стоит точка с запятой. Объявления компилятору хватает, чтобы проверить вызов triple(14): функция принимает int и возвращает int.
В triple.cpp записано определение (definition) — та же первая строка вместе с телом в фигурных скобках. Объявить функцию можно в каждом файле, который её вызывает, а определить — только в одном.
Кто сообщает об ошибке
Компилятор находит ошибки в тексте одного файла — например, пропущенную точку с запятой, как в программе с Hello, world. По объявлению он заметит и вызов triple(14, 1) с лишним числом. Объектный файл в таком случае не появляется.
Компоновщик видит программу целиком и ищет определение для каждой вызванной функции. Если triple.cpp не попал в сборку, main.cpp скомпилируется без ошибок, а компоновщик остановится:
undefined reference to `triple(int)'Так пишет GNU ld — стандартный компоновщик в Linux. В задачах курса то же сообщение выглядит иначе: undefined symbol: triple(int).
Сообщение компилятора указывает файл и строку, например main.cpp:6, а компоновщик называет функцию, у которой нет определения.
Частая ошибка
Имя в определении должно в точности совпадать с объявленным. Здесь в triple.cpp оно начинается с заглавной буквы:
int Triple(int x) { // ✗
return x * 3;
}Оба файла компилируются без ошибок, но компоновщик снова не находит triple(int). C++ различает заглавные и строчные буквы, поэтому Triple — другая функция, а у объявленной triple определения по-прежнему нет.