Перейти к основному содержимому

C++ - язык системного программирования

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…


Основы C++

Что такое C++?

C++ — это язык программирования со следующими особенностями:

  • Типизация — статическая, сильная; вывод типов есть (auto, decltype, шаблоны).
  • Парадигма — мультипарадигменный — процедурный (наследие C), объектно-ориентированный, обобщённое программирование (шаблоны), функциональные элементы (лямбды, алгоритмы STL).
  • Уровень — низкоуровневый с высокоуровневыми абстракциями: прямой доступ к памяти и железу при классах, шаблонах и RAII.
  • Выполнение — компилируемый в нативный машинный код (AOT); компиляторы MSVC, GCC, Clang.
  • Память — в первую очередь ручное (new/delete, malloc/free); идиоматично — RAII и владение (деструкторы, умные указатели, move semantics); GC и подсчёт ссылок в стандарте нет.
  • Платформа — кроссплатформенный (стандарт ISO C++); нативный код под целевую ОС и архитектуру; управляемого runtime нет.
  • Формат разработки — требует структуры проекта — единицы трансляции, заголовки, линковка, системы сборки (CMake, Meson, Make, Bazel).
  • Направление — системное программирование, встроенные системы, игры, HPC, финансы, драйверы и компоненты с жёсткими требованиями к производительности.
  • REPL — в стандарте нет; интерактивно — Cling (интерпретатор на базе LLVM), online-компиляторы (Compiler Explorer, Wandbox), фрагменты в IDE.
  • Поколение — классический (с 1983 года), активно развивающийся (цикл стандартов C++11, 14, 17, 20, 23, 26).
  • Параллелизм и асинхронность — нативные потоки (std::thread, std::async, std::future, std::atomic); корутины с C++20 (co_await); стандартного async/await как в JavaScript или C# нет; OpenMP и MPI — через библиотеки.
  • Безопасность — "опасный" — арифметика указателей, переполнения буферов, use-after-free, data races и неопределённое поведение (UB); гарантий memory safety как у Rust нет.

Если какой-то пункт из списка непонятен — подробные определения и примеры в Язык программирования.

Добро пожаловать в мир C++

Если вы читаете эту статью, значит вы решили познакомиться с одним из самых влиятельных языков программирования в истории компьютеров. Это отличный выбор!

Название C++ читается как "си плюс плюс". Символ ++ — это оператор постинкремента в C.

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

Исторически C++ задумывался как C с классами (идеи Simula, эффективность C). Типичный курс по ООП на C++ идёт от сложности ПО и объектной декомпозиции к инкапсуляции, наследованию и полиморфизму уже в синтаксисе языка — см. маршрут ООП и ООП в C++.

Важно

С++ - очень сложный язык. Он очень требователен к подготовке. У него высокий порог входа. Готовьтесь.

Код ITЗагрузка примера кода…

Суть в том, что в одной программе сочетаются классы и методы (ООП), RAII (ресурс живёт вместе с объектом), умные указатели (unique_ptr — владение без ручного delete), шаблоны (std::string, make_unique) — при этом код остаётся близким по производительности к низкоуровневому, но читается на более высоком уровне. Именно эта многопарадигмальность и контроль отличают C++.

Пример управления памятью:

int a = 10; // на стеке (автоудаление)
int* b = new int(20); // в куче (ручное удаление)
delete b; // обязательно!
std::unique_ptr<int> c = std::make_unique<int>(30); // умный указатель (сам удалит)
АЛГОРИТМ СобратьПрограммуCpp(исходники)
для каждого файл в исходники
объект := компилятор.скомпилировать(файл) // .o / .obj
конец
исполняемый := линковщик.связать(объекты, библиотеки)
вернуть исполняемый
КОНЕЦ
ЭтапРезультат
ПрепроцессорПодстановка #include, макросов
КомпиляцияОбъектный файл на одну единицу трансляции
ЛинковкаОдин .exe / бинарник с адресами символов

Основные концепты C++

  • Компиляция — исходный код переводится в машинный, что даёт высокую производительность.
  • Управление памятью — ручное через new/delete или умные указатели (unique_ptr, shared_ptr).
  • ООП — классы, наследование, полиморфизм, инкапсуляция.
  • Шаблоны — обобщённое программирование, generics.
  • STL — стандартная библиотека контейнеров и алгоритмов.
  • Многопоточностьstd::thread, мьютексы, атомарные операции.
  • Безопасность — строгая типизация, RAII, исключения; проверки границ — через at(), GSL, санитайзеры (не "из коробки" для каждого доступа).
  • Производительность — близко к C, минимальные накладные расходы, работа с памятью напрямую.

Всё это в одном языке.


Сложности в работе с C++

Помимо синтаксиса, в C++ важны сборка, ABI и единый toolchain: шаблоны, потоки и ручная память должны линковаться с одной runtime и одним стандартом.

При работе в C++ нужно учитывать:

  • работать с файлами в проекте;
  • обеспечивать совместимость компонентов (из-за разных поколений C++, многие решения и библиотеки могут не работать в новых версиях, вызывать ошибки и требовать настроек;
  • настраивать конфигурацию - дополнительные каталоги, оптимизация, предпроцессор, библиотеки времени выполнения, и прочее;
  • учитывать стандарт языка C++.

Важно не путать версию стандарта языка и бинарную совместимость (ABI). Исходный код C++14 обычно компилируется и с -std=c++20, а вот готовая .dll/.so, собранная другим компилятором, другой версией runtime или в другом режиме Debug/Release, может не слинковаться или упасть в рантайме.

// Библиотека, скомпилированная с C++14
class OldLibrary {
std::string name; // несовместимость чаще из-за компилятора, runtime и Debug/Release, не только -std=
};

// Ваш проект на C++20 подключает её — получаем:
// "undefined reference" или странные краши

Все компоненты проекта должны быть скомпилированы одним компилятором с одинаковыми настройками. Могут быть сложности с линковкой, разными компиляторами (MSVC, GCC, Clang), ABI (Application Binary Interface).

Настройка конфигурации - это лабиринт из тысяч опций. Откройте свойства любого С++ проекта в Visual Studio:

C/C++ → General → Additional Include Directories (где искать .h)
C/C++ → Preprocessor → Preprocessor Definitions (макросы: _DEBUG, _UNICODE)
C/C++ → Code Generation → Runtime Library (/MT, /MD, /MTd, /MDd)
C/C++ → Language → C++ Language Standard (C++14/17/20/23)
Linker → General → Additional Library Directories (где искать .lib)
Linker → Input → Additional Dependencies (какие .lib подключать)
Linker → Система → SubSystem (Console, Windows)

В реальности без правильных макросов библиотека не соберётся, порой в каждом заголовке приходится писать всякое, например:

// Без правильных макросов библиотека не соберётся
#ifdef _WIN32
#ifdef BUILD_DLL
#define API __declspec(dllexport)
#else
#define API __declspec(dllimport)
#endif
#else
#define API
#endif

// И это в каждом заголовке!

Эволюция стандарта (ориентир на 2026 год):

СтандартГод ISOПрактика
C++11/142011/2014legacy-код, embedded, долгоживущие проекты
C++172017широко в продакшене
C++202020дефолт для новых проектов (GCC 11+, Clang 14+, MSVC 19.29+)
C++232023актуальный "текущий" стандарт в свежих toolchain
C++26в работечерновик комитета

В корпоративной среде часто фиксируют минимальную версию стандарта в CI (например, C++17). Код с std::ranges (C++20) на проекте с -std=c++14 не соберётся — это ограничение toolchain, а не "несовместимость языков" как таковая. Генераторы кода без указания стандарта часто подставляют C++20 — проверяйте CMakeLists.txt / флаги компилятора.

Простое Hello World превращается в конфигурацию. Для выполнения кода:

#include <iostream>
int main() { std::cout << "Hello\n"; }

Нужно за кулисами:

  1. Выбрать компилятор (MSVC? Clang? MinGW?) — см. Компиляторы и toolchain
  2. Выбрать стандарт (/std:c++20 или -std=c++17)
  3. Выбрать Runtime Library (динамическую или статическую?)
  4. Настроить пути (если используем внешние библиотеки)
  5. Решить, как распространять (свои DLL или один .exe)
  6. Если на Linux — учесть другой ABI

Один неправильный флаг компиляции вызывает 50 неразрешённых внешних символов. Разные версии стандарта, по факту разные языки. А библиотека 2015 года может не слинковаться с проектом 2026 года.

И это мы ещё не говорили про разницу между Debug и Release. Собранная в Debug библиотека прекрасно работает с Debug-проектом, но при переключении на Release — пачка ошибок линковки. Потому что имена функций (mangling) различаются, а Runtime Library другая (/MTd и /MT).

Вот почему инженеры C++ ценятся так дорого, потому что они управляют такой сложностью.

Это язык для тех, кто готов взять на себя ответственность за каждый бит, каждый флаг компилятора и каждый стандарт за последние 40 лет. Такова плата за один из самых мощных языков в мире.


C with Classes

Бьёрн Страуструп, разрабатывая язык в Bell Labs в 1979–1985 годах, начал с эксперимента под названием C with Classes — надстройки над C, добавляющей поддержку классов, наследования и конструкторов/деструкторов. Совместимость с C изначально была условием выживания. Нельзя было требовать от инженеров переписывать ядра, драйвера и низкоуровневые библиотеки — следовало позволить им наращивать новую функциональность поверх уже написанного кода. Отсюда и фундаментальная особенность — большая часть кода на C компилируется как C++ при компиляции с -std=c++ (оговорки — ключевые слова C++ вроде class/new, более строгие правила приведения типов, отличия void* и перегрузки). Полная двусторонняя совместимость обеспечивается на уровне линковки через extern "C" и общий ABI платформы.

Но если C++ начался как C с классами, то к середине 1990-х он уже вышел далеко за пределы этой идеи. Шаблоны (введённые в 1991 г.), исключения, пространства имён, STL (Standard Template Library, принятая в стандарт в 1998 г.) — всё это самостоятельные парадигмы, вплетённые в язык. Важно понимать: C++ — это набор взаимосовместимых подъязыков, укладываемых в одну систему типов и одну модель компиляции. Программист может писать в стиле чистого процедурного C (что нередко делается в embedded-разработке), использовать объектно-ориентированную модель (как в большинстве фреймворков), применять обобщённое программирование через шаблоны (как в современных библиотеках вроде Eigen или Boost.Hana), или даже встраивать функциональные паттерны (через лямбда-выражения, появившиеся в C++11).


Почему C++ до сих пор актуален, несмотря на "более удобные" языки?

Чтобы ответить на этот вопрос, необходимо отделить удобство разработки от требований к исполнению. Языки вроде C#, Java, Python или Go решают одну ключевую задачу: повысить продуктивность программиста за счёт ограничений, абстракций и автоматизации. Сборка мусора, строгая типизация времени выполнения, управляемая среда — всё это снижает когнитивную нагрузку и уменьшает количество классов ошибок. Но цена за это — предсказуемость производительности.

C++ не гарантирует, что вы напишете код быстрее. Он гарантирует, что если вы знаете, что делаете, то получите максимально близкое к аппаратным возможностям время выполнения. Это достигается за счёт нескольких фундаментальных принципов:

  1. Отсутствие накладных расходов по умолчанию (zero-cost abstractions).
    Любая абстракция — будь то шаблонный контейнер std::vector, итератор или умный указатель — должна компилироваться в код, не уступающий по эффективности ручному управлению. Например, std::vector<T>::operator[] сводится к простой арифметике указателей — никаких проверок границ (если не включены отладочные режимы), никаких вызовов виртуальных функций. Это архитектурное требование — если абстракция не может быть реализована без overhead’а, она либо не вводится, либо предоставляется альтернатива без него.

  2. Прямой контроль над моделью памяти.
    C++ не навязывает кучу, стек и статическую память как единственно возможные области. Он предоставляет инструменты для работы с ними — new/delete, malloc/free, размещение объектов на стеке, placement new — и позволяет создавать собственные аллокаторы, менеджеры памяти, пулы. В играх, например, критически важно избегать фрагментации и пауз от сборки мусора; в реальном времени — гарантировать, что выделение памяти не приведёт к блокировке. В C++ это ожидаемо.

  3. Прозрачность компиляции и линковки.
    В отличие от JIT-компилируемых или интерпретируемых языков, C++ превращается в машинный код до запуска. Это позволяет профилировать, оптимизировать, анализировать ассемблерный вывод на этапе разработки. Для высоконагруженных систем (биржевые трейдинговые платформы, телекоммуникационные стеки, ядра ОС) критична скорость и предсказуемость: нельзя допустить, чтобы сборка мусора в Java-приложении внезапно вызвала задержку в 50 мс — на фондовом рынке это эквивалентно потере сделки. C++ позволяет доказать отсутствие таких пауз аналитически.

  4. Платформенная независимость без посредника.
    C++ не требует виртуальной машины. Исполняемый файл — это нативный код для целевой архитектуры (x86-64, ARM, RISC-V и др.). Это означает:

    • отсутствие зависимости от runtime’а (кроме стандартной библиотеки, которую можно статически линковать);
    • минимальный размер образа (в embedded-системах — килобайты);
    • возможность прямого доступа к регистрам, портам ввода-вывода, страницам памяти;
    • совместимость с legacy-кодом и бинарными интерфейсами (ABI), написанными на C и ассемблере.

Именно эти свойства делают C++ не заменяемым в таких областях, как:

  • Ядра операционных систем (Windows NT, Linux, macOS XNU — многие подсистемы написаны на C++ или сочетают C и C++);
  • Графические и игровые движки (Unreal Engine, Frostbite, Source 2 — полностью или частично на C++);
  • Браузерные движки (Chromium/V8, WebKit — C++ как основа);
  • СУБД (MySQL, MongoDB — движки на C++; PostgreSQL — ядро в основном на C, клиенты и расширения часто на C++);
  • Системы реального времени и embedded (автомобильные контроллеры, авиаэлектроника, робототехника);
  • Высокочастотный трейдинг (где каждая наносекунда — деньги).

Да, C# (а также Rust, Zig, Go) успешно конкурируют в смежных нишах — например, в серверной разработке или инструментарии. Но в тех случаях, где требуется абсолютный контроль над выполнением — C++ остаётся одним из промышленно апробированных вариантов с 40-летней историей развития и огромной экосистемой (компиляторы, отладчики, профайлеры, статические анализаторы). ABI не универсален: совместимость бинарников зависит от компилятора, toolset (MSVC v142/v143 и т.д.), стандартной библиотеки и флагов вроде _GLIBCXX_USE_CXX11_ABI; между GCC, Clang и MSVC единого "C++ ABI" нет — для стабильных границ DLL часто используют C-интерфейс (extern "C").


Архитектурные основы

Прежде чем говорить о синтаксисе, важно понять модель компиляции — ту "машину", внутри которой живёт C++-код. В отличие от языков с единым загрузчиком (например, JVM), C++ опирается на многоступенчатую, децентрализованную схему:


1. Единица трансляции (translation unit)

Программа на C++ состоит из множества единиц трансляции. Каждая такая единица — это один .cpp-файл после предварительной обработки (#include, #define, условная компиляция). То есть:

// main.cpp
#include <iostream>
#include "utils.h"

int main() { /* ... */ }

после #include превращается в один большой текст, содержащий всё содержимое <iostream>, utils.h и собственного кода. Этот текст и есть единица трансляции.

Ключевая особенность: каждая единица трансляции компилируется независимо. Нет глобального анализа всей программы на этапе компиляции. Это накладывает ограничения (например, невозможность оптимизировать вызовы между единицами без LTO), но даёт гигантский выигрыш в скорости сборки и масштабируемости — в проектах с миллионами строк можно перекомпилировать только изменившиеся .cpp-файлы.


2. Заголовочные файлы

.h- или .hpp-файлы в C++ — это спецификации интерфейсов, передаваемые между единицами трансляции. Они содержат:

  • объявления функций и классов (не определения);
  • inline-функции и шаблоны (которые должны быть видны во всех единицах, где используются);
  • constexpr-константы;
  • директивы #pragma once или include guards (защита от множественного включения).

Заголовок — это договор — "если ты мне подключишь этот файл, я гарантирую, что в линковке будет найдена реализация этих символов". Нарушение этого договора (например, определение глобальной переменной в заголовке без inline) приводит к ошибкам линковки.


3. Компиляция → Линковка → Выполнение

Этапы:

  • Предобработка (cpp): раскрытие макросов, включение файлов.
  • Компиляция (g++ -c main.cpp): превращение единицы трансляции в объектный файл (.o / .obj), содержащий машинный код и таблицу символов (имена функций, глобальных переменных).
  • Линковка (ld, link.exe): объединение всех объектных файлов и библиотек в исполняемый файл или библиотеку. На этом этапе разрешаются внешние ссылки (например, вызов printf связывается с реализацией из libc).
  • Запуск — загрузка кода в память, инициализация глобальных объектов (в порядке определения внутри единицы, но неопределённом между ними!), вызов main().

Важно: глобальные объекты инициализируются до main(), и порядок их инициализации между разными единицами трансляции не определён. Это одна из самых тонких и опасных особенностей C++ (т.н. "static initialization order fiasco").


RAII и стандартная библиотека

RAII — ресурс привязан к времени жизни объекта. Подробнее: Управление памятью.

STL — контейнеры, итераторы и алгоритмы. Подробнее: Библиотеки.


В рунет-дискурсе

C++ в рунетских форумах часто рисуют как "язык гуру" и бесконечной сложности — указатели, шаблоны, segfault. Мем отражает реальную цену контроля над памятью и ABI, но перенос на любого пользователя C++ несправедлив — тот же язык держит игры, браузеры, БД и embedded.

Шутки про "настоящий программист на C++" — социальный маркер элитарности; в команде важнее читаемый код, тесты и стандарт C++ под проект. Указатель — Неолурк (C++); смежный низкий уровень — язык C.


Что читать дальше

ТемаСтатья
Типы, операторы, циклыТипы данных в C++, Операторы и выражения в C++, Циклы и управляющие конструкции в C++
ООП и памятьОбъектно-ориентированное программирование в C++, Управление памятью в C++
Функции, системноеФункции и лямбда-выражения в C++, Системное программирование на C++
Сборка, потоки, сетьКонфигурация и сборка в C++, Многопоточность и асинхронное выполнение в C++, Сетевое взаимодействие в C++
УглублениеC++ — углублённые темы
Идиомы, rangesИдиомы современного C++, Диапазоны и представления в C++20
Vulkan, Qt QuickVulkan и низкоуровневая графика на C++, Qt Quick — первая программа на QML
ПрактикаПрактические задания по C++
СправочникСправочник по C++