Перейти к содержимому
Теория · Первая программа · урок 5 из 7

Компиляция и компоновка

Вспомните переводчика и переплётчика из первого урока. Толстую книгу переводят по главам, и переводчик каждой главы видит только её. Если в первой главе написано «см. главу 3», переводчик верит ссылке на слово. Найдётся ли третья глава, проверит переплётчик, когда будет сшивать книгу целиком.

Программа на C++ часто состоит из нескольких .cpp-файлов, как книга из глав. Компилятор переводит каждый файл отдельно и, читая один, не видит, что написано в соседнем. Но вызов функции компилируется, только если компилятор уже знает, что такая функция есть. Вот программа из двух файлов: main.cpp вызывает функцию triple, а её тело лежит в triple.cpp.

Файл main.cpp:

C++
#include <iostream>

int triple(int x);

int main() {
    std::cout << triple(14) << '\n';  // 42
}

Файл triple.cpp:

C++
int triple(int x) {
    return x * 3;
}

Компиляцию обоих файлов и их компоновку в одну программу вместе называют сборкой.

Объявление и определение

Строка int triple(int x); в main.cpp — это объявление, та самая ссылка «см. главу 3». Оно повторяет первую строку функции:

  • тип результата;
  • имя;
  • то, что функция принимает, в круглых скобках.

Вместо тела стоит точка с запятой. Объявления компилятору хватает, чтобы проверить вызов triple(14): функция принимает int и возвращает int.

В triple.cpp записано определение — та же первая строка вместе с телом в фигурных скобках. Объявить функцию можно в каждом файле, который её вызывает, а определить — только в одном.

Как работает вызов

Давайте разберём, что происходит при вызове triple(14):

  1. x в int triple(int x) — это параметр, переменная функции. При вызове в неё попадает значение из скобок, здесь 14.
  2. return x * 3; вычисляет 14 * 3 и завершает функцию.
  3. Результат 42 возвращается на место вызова, и std::cout печатает его.

Если параметров несколько, их перечисляют через запятую: int add(int a, int b). Операторы +, - и * работают с целыми числами как в арифметике.

Кто сообщает об ошибке

Компилятор находит ошибки в тексте одного файла — например, пропущенную точку с запятой, как в программе с Hello, world. По объявлению он заметит и вызов triple(14, 1) с лишним числом. Объектный файл в таком случае не появляется.

Компоновщик видит программу целиком и ищет определение для каждой вызванной функции. Если triple.cpp не попал в сборку, main.cpp скомпилируется без ошибок, а компоновщик остановится — как переплётчик, у которого нет третьей главы:

TEXT
undefined reference to `triple(int)'

Так пишет GNU ld — стандартный компоновщик в Linux. В задачах курса то же сообщение выглядит иначе: undefined symbol: triple(int).

Соберите программу ниже, потом уберите triple.cpp из сборки и следите, какой объектный файл определяет нужную main.o функцию triple(int) и что пишет компоновщик:

ВживуюСборка из двух файловещё не собрано
исходные файлы
main.cppв сборке всегда
triple.cpp
объектные файлы
ещё не собрано
компоновщик
ещё не собрано

Компилятор переведёт каждый .cpp-файл отдельно, а компоновщик соберёт объектные файлы в программу. Нажмите кнопку сборки.

Уберите triple.cpp из сборки и соберите снова

Сообщение компилятора указывает файл и строку, например main.cpp:6, а компоновщик называет функцию, у которой нет определения.

Частая ошибка

Имя в определении должно в точности совпадать с объявленным. Здесь в triple.cpp оно начинается с заглавной буквы:

C++
int Triple(int x) {  // ✗
    return x * 3;
}

Оба файла компилируются без ошибок, но компоновщик снова не находит triple(int). C++ различает заглавные и строчные буквы, поэтому Triple — другая функция, а у объявленной triple определения по-прежнему нет.

Главное

  • Компилятор переводит каждый .cpp-файл отдельно, и для вызова ему хватает объявления.
  • Компоновщик ищет определение каждой вызванной функции во всей программе.
  • Нет определения — ошибка компоновщика undefined reference, хотя каждый файл скомпилировался.
  • Параметр получает значение из вызова, а return отдаёт результат на место вызова.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом