Сборка первой программы на C++ [1.4]
В этом уроке вы пройдете путь от пустого файла до запуска готовой программы на C++. Мы разберем виды целевых объектов (исполняемый файл, статическая и динамическая библиотеки), познакомимся с конфигурациями Debug/Release и шаг за шагом соберем проект с помощью CMake. В конце вас ждут практические задачи и чек-лист самопроверки.
Главная мысль урока: сборка -- это превращение исходного кода (.cpp) в машинный файл, который может быть исполняемым (.exe), статической (.lib / .a) или динамической (.dll / .so) библиотекой. Управлять этим процессом удобнее всего через CMakeLists.txt.
Содержание
1. Цели урока
- Понять, как из исходного кода (
.cpp) получаются целевые объекты. - Различать исполняемый файл, статическую и динамическую библиотеки.
- Объяснить разницу между конфигурациями
DebugиRelease. - Самостоятельно написать
CMakeLists.txtи собрать проект. - Переключать тип целевого объекта и конфигурацию сборки в CMake.
2. Что такое сборка программы
Сборка (build) -- это процесс превращения текстовых файлов с исходным кодом в машинные инструкции, которые процессор может выполнить напрямую. Общая схема выглядит так:
# Схема сборки (упрощенно)
#
# Исходный код (.cpp / .h)
# |
# v
# Компилятор (g++, clang++, MSVC)
# |
# v
# Целевой объект (.exe / .lib / .dll)
Компилятор читает ваш .cpp-файл, проверяет синтаксис, преобразует конструкции языка в машинный код и записывает результат в целевой объект (target). Тип этого объекта зависит от настроек проекта.
Сборка и запуск -- не одно и то же
3. Виды целевых объектов
Когда компилятор обрабатывает исходный код, результатом может быть один из трех типов файлов. Выбор зависит от того, как вы настроили проект.
3.1. Исполняемый файл (Executable)
Набор машинных инструкций, которые ОС может запустить напрямую. Это именно то, что вы запускаете, чтобы программа начала работать.
# CMake: создать исполняемый файл
add_executable(my_robot)
| Платформа | Расширение | Пример |
|---|---|---|
| Windows | .exe |
my_robot.exe |
| Linux | без расширения | my_robot |
| macOS | без расширения / .app |
my_robot |
3.2. Статическая библиотека (Static Library)
Набор подпрограмм, которые встраиваются в исполняемый файл на этапе компоновки (линковки). После сборки вашему .exe уже не нужен отдельный файл библиотеки -- весь ее код "вшит" внутрь.
# CMake: создать статическую библиотеку
add_library(my_math) # по умолчанию -- STATIC
# или явно:
add_library(my_math STATIC)
3.3. Динамическая библиотека (Shared / Dynamic Library)
Набор подпрограмм, которые подгружаются в программу при ее запуске (или по запросу). Библиотека остается отдельным файлом и не копируется внутрь исполняемого файла.
# CMake: создать динамическую библиотеку
add_library(my_sensors SHARED)
.dll / .so должен быть доступен.3.4. Сравнительная таблица
| Критерий | Исполняемый файл | Статическая библиотека | Динамическая библиотека |
|---|---|---|---|
| Можно запустить напрямую | Да | Нет | Нет |
Встраивается в .exe |
-- | Да | Нет |
| Загружается при запуске | -- | Нет | Да |
Размер итогового .exe |
Средний | Большой | Малый |
| Самодостаточность | Да | Да | Нет |
| Расширение (Win / Linux) | .exe / -- |
.lib / .a |
.dll / .so |
3.5. Типичные ошибки
Ошибка 1: попытка "запустить" библиотеку
Начинающие иногда собирают проект как библиотеку (add_library), а затем пытаются запустить полученный .lib / .a-файл. Библиотека не является самостоятельной программой -- у нее нет точки входа main().
# [!] Не сработает:
./build/Debug/my_math.a
# bash: ./build/Debug/my_math.a: Permission denied (или "не является исполняемым")
# [ok] Библиотеку нужно подключить к исполняемому файлу:
# target_link_libraries(my_robot PRIVATE my_math)
Ошибка 2: одновременно add_executable и add_library с одним именем
Если в CMakeLists.txt указано и add_executable(lesson3), и add_library(lesson3), CMake выдаст ошибку: цель с именем lesson3 уже определена. Активной должна быть только одна команда.
# [!] Ошибка: дублирование цели
add_executable(lesson3)
add_library(lesson3) # CMake Error: target "lesson3" already exists
# [ok] Оставьте только один вариант:
add_executable(lesson3)
# add_library(lesson3)
4. Конфигурации сборки: Debug и Release
Один и тот же исходный код компилятор может превратить в машинный код по-разному в зависимости от выбранной конфигурации. Две основные конфигурации -- Debug и Release.
4.1. Режим Debug (Отладка)
Предназначен для разработки и поиска ошибок.
- Оптимизация отключена -- код выполняется "как написан", без перестановок компилятором.
- Генерируется отладочная информация -- можно ставить точки останова, видеть значения переменных и стек вызовов.
- Итоговый файл больше по размеру и работает медленнее.
# Установка режима Debug в CMake
set(CMAKE_BUILD_TYPE Debug)
4.2. Режим Release (Релиз)
Предназначен для финальной версии программы.
- Оптимизация включена -- компилятор перестраивает код для максимальной скорости.
- Отладочная информация не генерируется -- файл меньше, код сложнее разобрать.
- Итоговый файл меньше и работает быстрее.
# Установка режима Release в CMake
set(CMAKE_BUILD_TYPE Release)
4.3. Сравнение режимов
| Параметр | Debug | Release |
|---|---|---|
| Оптимизация | Отключена | Максимальная |
| Отладочная информация | Генерируется | Отсутствует |
| Размер файла | Большой | Малый |
| Скорость работы | Низкая | Высокая |
| Назначение | Разработка, тесты | Продакшен |
Debug, пока пишете и тестируете код робота, и переключайтесь на Release, когда загружаете финальную прошивку.4.4. Типичные ошибки
Ошибка 1: отладка Release-сборки
Если вы собрали проект в режиме Release и пытаетесь поставить точку останова (breakpoint) в отладчике, он может "прыгать" по строкам или не останавливаться вовсе. Причина -- оптимизатор переставил и сократил код, а отладочные символы отсутствуют.
# [!] Собрали Release, а потом пытаемся отлаживать -- не работает
set(CMAKE_BUILD_TYPE Release)
# [ok] Для отладки нужен Debug
set(CMAKE_BUILD_TYPE Debug)
Ошибка 2: поставка Debug-сборки пользователю
Debug-версия содержит отладочные символы, работает медленнее и весит значительно больше. Для финальной поставки (на робота, заказчику и т.д.) всегда используйте Release.
5. Система сборки CMake
CMake -- это кроссплатформенная система управления сборкой. Она не компилирует код сама, а генерирует файлы для выбранной системы сборки (Make, Ninja, Visual Studio и др.), которая уже вызывает компилятор.
# Как работает CMake (упрощенно):
#
# CMakeLists.txt --> CMake --> Makefile / .sln / ninja --> Целевой объект
# (ваши правила) (системные файлы) (.exe / .lib / .dll)
Главное преимущество: один и тот же CMakeLists.txt работает на Windows, Linux и macOS. Вам не нужно писать отдельные скрипты сборки под каждую платформу.
5.1. Основные команды CMakeLists.txt
| Команда | Назначение |
|---|---|
cmake_minimum_required(VERSION X.Y) |
Минимально допустимая версия CMake |
project(name ...) |
Метаданные проекта: имя, версия, язык |
set(VAR value) |
Создает или изменяет переменную |
add_executable(name) |
Объявляет цель типа "исполняемый файл" |
add_library(name) |
Объявляет цель типа "статическая библиотека" |
add_library(name SHARED) |
Объявляет цель типа "динамическая библиотека" |
target_sources(name PRIVATE ...) |
Привязывает исходные файлы к целевому объекту |
PRIVATE в target_sources означает, что перечисленные файлы используются только для сборки данной цели и не передаются зависимым целям.5.2. Типичные ошибки
Ошибка 1: файл называется не CMakeLists.txt
CMake ищет файл с точным именем CMakeLists.txt (регистр важен!). Варианты вроде cmakelists.txt, CMakeList.txt или cmake_lists.txt не будут найдены.
# [!] Файл назван неправильно
$ ls
cmake_lists.txt main.cpp
$ cmake -B build
# CMake Error: The source directory "..." does not appear to contain CMakeLists.txt.
# [ok] Правильное имя (с точным регистром)
$ mv cmake_lists.txt CMakeLists.txt
$ cmake -B build
# -- Configuring done
Ошибка 2: пробел в ${ sources }
В CMake обращение к переменной пишется слитно: ${sources} . Если между скобками и именем есть пробелы (${ sources }), CMake может не найти переменную.
# [!] Пробелы внутри ${ } -- переменная не раскроется корректно
target_sources(lesson3 PRIVATE ${ sources })
# [ok] Без пробелов
target_sources(lesson3 PRIVATE ${sources})
6. Пошаговая сборка проекта
Теперь соберем все знания вместе и пройдем путь от пустой папки до работающей программы.
Шаг 1. Структура директории
Создайте папку проекта с двумя файлами:
# Создаем директорию и переходим в нее
mkdir my_first_project
cd my_first_project
# Создаем два пустых файла
# Linux / macOS:
touch CMakeLists.txt main.cpp
# Windows (PowerShell):
# New-Item CMakeLists.txt, main.cpp
Итоговая структура:
my_first_project/
CMakeLists.txt # инструкции для системы сборки
main.cpp # исходный код программы
Шаг 2. Исходный код main.cpp
Откройте main.cpp в редакторе и напишите простейшую программу:
// main.cpp -- первая программа на C++
#include <iostream> // Подключаем библиотеку ввода-вывода
int main() {
// Выводим приветствие в консоль
std::cout << "Hello, Robotics World!" << std::endl;
return 0; // 0 -- программа завершилась успешно
}
| Строка | Что делает |
|---|---|
#include <iostream> |
Подключает стандартную библиотеку для работы с вводом/выводом |
int main() |
Объявляет главную функцию -- точку входа в программу |
std::cout << "..." |
Выводит текст в стандартный поток (консоль) |
std::endl |
Переводит строку и очищает буфер вывода |
return 0; |
Сообщает ОС, что программа завершилась без ошибок |
Шаг 3. Файл CMakeLists.txt
Это "рецепт" для системы сборки. Скопируйте код ниже и изучите комментарии:
# -- 1. Системные требования --
cmake_minimum_required(VERSION 3.18)
# -- 2. Описание проекта --
project(
lesson3 # Имя проекта
VERSION 0.0.1 # Версия (Major.Minor.Patch)
DESCRIPTION "First project" # Краткое описание
LANGUAGES CXX # Язык: CXX = C++
)
# -- 3. Настройки компиляции --
set(CMAKE_CXX_STANDARD 17) # Стандарт C++17
set(CMAKE_CXX_STANDARD_REQUIRED ON) # Стандарт обязателен
set(CMAKE_BUILD_TYPE Debug) # Конфигурация: Debug
# -- 4. Целевой объект --
# Вариант 1: Исполняемый файл (.exe)
add_executable(lesson3)
# Вариант 2: Статическая библиотека (.lib / .a)
# add_library(lesson3)
# Вариант 3: Динамическая библиотека (.dll / .so)
# add_library(lesson3 SHARED)
# -- 5. Исходные файлы --
set(sources
main.cpp
)
target_sources(lesson3 PRIVATE ${sources})
#) и раскомментируйте нужную. Одновременно активна только одна из трех команд.Шаг 4. Генерация и компиляция
Откройте терминал в папке проекта и выполните две команды:
# 1. Генерируем файлы сборки в папку build
cmake -B build
# 2. Запускаем компиляцию
cmake --build build
После успешной сборки появится структура:
my_first_project/
CMakeLists.txt
main.cpp
build/ # создана автоматически
Debug/ # или Release
lesson3.exe # ваш целевой объект
Шаг 5. Запуск результата
# Windows:
.\build\Debug\lesson3.exe
# Linux / macOS:
./build/lesson3
Ожидаемый вывод:
Hello, Robotics World!
Типичные ошибки при сборке
Ошибка: компилятор не найден
Если при команде cmake -B build появляется сообщение "No CMAKE_CXX_COMPILER could be found", значит, в системе не установлен или не настроен компилятор C++.
# Проверить наличие компилятора:
g++ --version # Linux / macOS (GCC)
cl # Windows (MSVC, из Developer Command Prompt)
# Если не установлен -- установите:
# Ubuntu/Debian: sudo apt install g++
# macOS: xcode-select --install
# Windows: установите Visual Studio (с компонентом "Разработка на C++")
Ошибка: main не найдена при сборке библиотеки
Если вы выбрали add_library, но оставили main() в коде, сборка пройдет успешно (библиотеке не нужна точка входа). Однако запустить .lib-файл вы не сможете. И наоборот: если выбран add_executable, а функция main() отсутствует -- линковщик выдаст ошибку.
# [!] Ошибка линковщика при отсутствии main() в executable:
# undefined reference to `main'
# [ok] Убедитесь, что в main.cpp есть функция int main() { ... }
7. Практика: типовые задачи (с решениями)
Базовые задачи
Задача 1: "Мое имя"
Измените программу так, чтобы она выводила ваше имя и специальность.
#include <iostream>
int main() {
std::cout << "My name is Alex." << std::endl;
std::cout << "I study robotics." << std::endl;
return 0;
}
// Output:
// My name is Alex.
// I study robotics.
Задача 2: Несколько строк через \n
Выведите три строки текста, используя символ \n вместо std::endl.
#include <iostream>
int main() {
std::cout << "Line 1\nLine 2\nLine 3\n";
return 0;
}
// Output:
// Line 1
// Line 2
// Line 3
Задачи на CMake
Задача 3: Переключите конфигурацию на Release
Измените CMakeLists.txt так, чтобы проект собирался в режиме Release. Соберите и сравните размер файла с Debug-версией.
# Было:
set(CMAKE_BUILD_TYPE Debug)
# Стало:
set(CMAKE_BUILD_TYPE Release)
# Пересоберите:
# cmake -B build
# cmake --build build
Задача 4: Соберите проект как статическую библиотеку
Переключите целевой объект на статическую библиотеку. Убедитесь, что в папке build появился файл .lib (Windows) или .a (Linux).
# Закомментируйте executable:
# add_executable(lesson3)
# Раскомментируйте library:
add_library(lesson3)
# Пересоберите:
# cmake -B build
# cmake --build build
# Результат: build/Debug/lesson3.lib (Windows)
# build/liblesson3.a (Linux)
Продвинутые задачи
Задача 5: Два исходных файла
Добавьте в проект файл greet.cpp с функцией, которая возвращает строку приветствия. Подключите его к сборке через переменную sources.
greet.h
#ifndef GREET_H
#define GREET_H
#include <string>
std::string getGreeting();
#endif
greet.cpp
#include "greet.h"
std::string getGreeting() {
return "Hello from greet module!";
}
main.cpp
#include <iostream>
#include "greet.h"
int main() {
std::cout << getGreeting() << std::endl;
return 0;
}
// Output:
// Hello from greet module!
CMakeLists.txt (изменения)
set(sources
main.cpp
greet.cpp
)
target_sources(lesson3 PRIVATE ${sources})
Задача 6: Динамическая библиотека
Соберите проект как динамическую библиотеку и найдите получившийся .dll / .so-файл в папке build.
# Закомментируйте executable:
# add_executable(lesson3)
# Раскомментируйте shared library:
add_library(lesson3 SHARED)
# Пересоберите:
# cmake -B build
# cmake --build build
# Результат: build/Debug/lesson3.dll (Windows)
# build/liblesson3.so (Linux)
8. Чек-лист самопроверки знаний
Отметьте пункты, которые вы действительно понимаете и можете применить без подсказок.
| +/- | Навык | Проверка |
|---|---|---|
| Понятие сборки | Могу объяснить, что такое сборка и чем она отличается от запуска программы | |
| Исполняемый файл | Могу объяснить, что такое .exe и как его создать через CMake (add_executable) |
|
| Статическая библиотека | Могу объяснить, чем .lib / .a отличается от .exe и когда ее использовать |
|
| Динамическая библиотека | Могу объяснить, чем .dll / .so отличается от статической библиотеки |
|
| Конфигурация Debug | Могу объяснить, почему Debug-сборка медленнее и тяжелее, но удобнее для отладки | |
| Конфигурация Release | Могу переключить проект на Release и объяснить, зачем это нужно | |
Структура CMakeLists.txt |
Могу написать CMakeLists.txt с нуля для простого проекта на C++17 |
|
| Команды CMake в терминале | Могу выполнить cmake -B build и cmake --build build и понимаю, что делает каждая |
|
| Переключение типа цели | Могу переключить сборку между executable, static library и shared library |
|
| Многофайловый проект | Могу добавить второй .cpp-файл в проект через переменную sources |
|
| Поиск ошибок сборки | Могу прочитать ошибку компилятора/линковщика и понять, к какому файлу и строке она относится | |
Папка build |
Могу найти скомпилированный файл в папке build и запустить его из терминала |