Menu

Компиляция C++: от исходного кода к исполняемому файлу с g++

Как C++ превращает исходник .cpp в нативный исполняемый файл: компиляция с g++ (или clang++/MSVC), запуск бинарника и чтение ошибок компилятора, когда что-то идёт не так.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Сначала компиляция, потом запуск

Теперь, когда у вас установлен компилятор, запуск программы на C++ — это цикл из двух шагов. В отличие от скриптового языка, где интерпретатор читает ваш файл строка за строкой, C++ сначала компилирует исходный код в нативный исполняемый файл — самостоятельный бинарник из машинного кода — а затем вы запускаете этот бинарник напрямую. Во время выполнения между вашей программой и процессором нет никакого интерпретатора.

Именно из-за этого разделения C++ быстрый, и именно поэтому синтаксическая ошибка останавливает вас на этапе компиляции, а не на середине выполнения. Компилятор проверяет весь файл, прежде чем что-либо создать.

Любой самодостаточный пример можно запустить прямо здесь, на странице — редактор ниже компилирует и запускает его за вас. Но стоит знать, что происходит на вашей собственной машине, потому что именно там живут настоящие проекты.

Цикл «скомпилировать и запустить» на вашей машине

Допустим, вы сохранили это как main.cpp:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello from the terminal" << endl;
    return 0;
}

Откройте терминал в папке с файлом и вызовите компилятор. Самый распространённый компилятор — g++ (часть GCC); clang++ работает абсолютно так же для всего, что здесь описано:

g++ main.cpp -o main

Если код компилируется без ошибок, g++ ничего не выводит и создаёт рядом с исходником новый файл: исполняемый файл с именем main (или main.exe на Windows). Этот бинарник — машинный код, не читаемый текст, и он привязан к вашей операционной системе и процессору. Теперь запустите его:

./main
Hello from the terminal

На Windows вы бы запустили его как main.exe (или просто main) из того же терминала. ./ на macOS и Linux говорит оболочке: «программа прямо здесь, в этой папке, а не где-то в PATH» — шаг, который новички забывают, а потом недоумевают, почему main отвечает command not found.

Что делает -o (и a.out)

Флаг -o задаёт имя выходного файла. Опустите его — и g++ всё равно скомпилирует, но запишет исполняемый файл под именем по умолчанию: a.out на macOS/Linux, a.exe на Windows.

g++ main.cpp        # produces a.out, not main
./a.out

Это постоянно сбивает людей с толку: они компилируют, не видят ошибок, запускают ./main и получают No such file or directory — потому что бинарник на самом деле называется a.out. Всегда указывайте -o, чтобы точно знать, что вы запускаете.

Вы компилируете один раз на каждое изменение исходника. После этого исполняемый файл независим — можно запускать ./main хоть сто раз без перекомпиляции. Заново вызывать g++ нужно только после того, как вы отредактировали файл .cpp.

Выбор стандарта C++

У C++ есть версии — C++11, C++14, C++17, C++20, C++23 — каждая добавляет возможности в язык. Подвох в том, что ваш компилятор выбирает стандарт по умолчанию, который может быть старее, чем вы ожидаете, поэтому современный код может не скомпилироваться без видимой причины. Задайте стандарт явно через -std:

g++ -std=c++17 main.cpp -o main
g++ -std=c++20 main.cpp -o main

Вот код, использующий возможность C++17 (structured bindings). В редакторе он компилируется без проблем, но на вашей машине ему нужен -std=c++17 или новее:

Если вы когда-нибудь увидите ошибку вроде 'structured bindings' only available with '-std=c++17', решение не в вашем коде — оно в том, чтобы добавить правильный флаг -std. На протяжении всего этого курса считайте, что у вас C++17 или новее.

Включите предупреждения

Программа на C++ может скомпилироваться без ошибок и всё равно быть неправильной. Компилятор, если его попросить, укажет на подозрительный код раньше, чем тот укусит вас во время выполнения. Добавьте -Wall -Wextra:

g++ -std=c++17 -Wall -Wextra main.cpp -o main

Посмотрите на эту программу. Без -Wall она компилируется, но в ней есть реальная ошибка — чтение переменной, которой никогда не присваивалось значение, а это неопределённое поведение:

#include <iostream>
using namespace std;

int main() {
    int count;                 // never initialized
    cout << count << endl;     // reads garbage - undefined behavior
    return 0;
}

С включёнными предупреждениями компилятор отмечает это:

warning: 'count' is used uninitialized [-Wuninitialized]

Возьмите за привычку компилировать с -Wall -Wextra с самого первого дня. Предупреждения — это бесплатное код-ревью от компилятора; игнорировать их — значит позволять тонким ошибкам выживать. Здесь исправление — просто int count = 0;.

Чтение ошибок компилятора

Когда g++ отвергает ваш код, он называет файл, строку и что именно пошло не так. Умение читать эти сообщения — половина дела, чтобы выбраться из тупика. Вот классика — пропущенная точка с запятой:

#include <iostream>
using namespace std;

int main() {
    cout << "Oops"      // no semicolon
    return 0;
}
main.cpp:5:5: error: expected ';' before 'return'
    5 |     return 0;
      |     ^~~~~~

Несколько вещей, на которые стоит обратить внимание. Ошибка указывает на строку 5, но ошибка — в строке 4: компилятор понимает, что точки с запятой не хватает, лишь дойдя до следующего токена. Поэтому, когда ошибка указывает на строку, которая выглядит нормально, проверьте строку выше. main.cpp:5:5 — это файл, строка, затем столбец. Исправьте ровно то одно, что он называет, и перекомпилируйте — не поддавайтесь желанию гадать.

Ошибки компилятора означают, что ничего ещё не запускалось. Они отличаются от ошибок времени выполнения, когда программа стартовала, а потом упала. Ловить ошибки на этапе компиляции, до того как программа вообще запустится, — одна из главных сильных сторон C++.

Программа-проверка

Запустите это в редакторе или сохраните как main.cpp и выполните g++ -std=c++17 -Wall main.cpp -o main, затем ./main на своей машине. Если появились все три строки, ваш набор инструментов работает от начала до конца:

Здесь появляются три вещи, с которыми вы как следует познакомитесь совсем скоро: переменная int, vector (массив C++ с изменяемым размером) и cout для вывода. Пока достаточно того, что программа компилируется и печатает все три строки по порядку.

Далее: синтаксис C++

Вы скомпилировали и запустили несколько программ, но мы пробежались мимо пунктуации — строк #include, фигурных скобок, точек с запятой, int main() и того, почему каждая строка выглядит именно так. Следующая страница разбирает синтаксис C++ по частям, чтобы структура перестала казаться шаблоном и начала обретать смысл.

Часто задаваемые вопросы

Как скомпилировать и запустить программу на C++?

Сохраните код как main.cpp, откройте терминал в этой папке и выполните g++ main.cpp -o main, чтобы получить исполняемый файл. Затем запустите его командой ./main на macOS/Linux или main.exe на Windows. Компилируете вы один раз; полученный бинарник можно запускать сколько угодно раз.

Что делает флаг -o в g++?

-o задаёт имя выходного файла. g++ main.cpp -o hello создаёт исполняемый файл с именем hello. Если опустить -o, g++ по умолчанию использует имя a.out (или a.exe на Windows) - именно поэтому новички часто запускают файл, о создании которого даже не подозревали.

Как скомпилировать C++ под конкретный стандарт, например C++17 или C++20?

Передайте флаг -std: g++ -std=c++17 main.cpp -o main или -std=c++20. Без него компилятор берёт собственный стандарт по умолчанию, который может быть старее, чем вы ожидаете, поэтому новые возможности вроде structured bindings или <ranges> могут не компилироваться, пока вы не зададите стандарт явно.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ