старейший из трёх, стандарт для Linuxthe oldest of the three, the Linux standard
Свободный компилятор, начатый Ричардом Столлманом в 1987 году, сегодня — единый бэкенд для десятка языковых фронтендов и компилятор по умолчанию для ядра Linux и большинства дистрибутивов. Монолитная архитектура с закрытыми для внешнего переиспользования internals — прямая противоположность философии Clang/LLVM.A free compiler started by Richard Stallman in 1987, today a single backend serving a dozen language frontends and the default compiler for the Linux kernel and most distributions. A monolithic architecture with internals not designed for external reuse — the direct opposite of Clang/LLVM's philosophy.
Ричард Столлман написал первую версию в 1987 году как часть GNU-проекта — свободную альтернативу проприетарным компиляторам Unix того времени, когда даже базовые инструменты разработки часто были платными и закрытыми. Изначально GCC расшифровывался как GNU C Compiler, но по мере добавления фронтендов для других языков (Fortran в 1990-х, затем C++, Objective-C, Ada, Java, Go, D) название сменили на GNU Compiler Collection, сохранив аббревиатуру. Rust получил экспериментальный фронтенд gccrs относительно недавно — попытка дать Rust второй, независимый от LLVM компилятор.Richard Stallman wrote the first version in 1987 as part of the GNU project — a free alternative to the proprietary Unix compilers of the era, when even basic development tools were often paid and closed. GCC originally stood for GNU C Compiler, but as frontends for other languages were added (Fortran in the 1990s, then C++, Objective-C, Ada, Java, Go, D), the name was changed to GNU Compiler Collection, keeping the acronym. Rust recently got an experimental frontend, gccrs — an attempt to give Rust a second compiler independent of LLVM.
Значимый исторический эпизод — форк EGCS (1997): часть сообщества откололась от официального GCC из-за медленного темпа разработки под управлением FSF, EGCS развивался быстрее и в итоге был принят обратно как официальный GCC 2.95 (1999) — редкий пример форка, который «выиграл» и слился обратно с оригиналом.A significant historical episode is the EGCS fork (1997): part of the community split off from official GCC over the slow pace of development under FSF stewardship; EGCS moved faster and was eventually adopted back as official GCC 2.95 (1999) — a rare example of a fork that "won" and merged back into the original.
Команда g++ main.cpp -o main — не один процесс, а координатор (driver), запускающий цепочку отдельных программ. Понимание этой цепочки объясняет, что именно измеряют флаги вроде -### (напечатать команды, не выполняя их) и почему некоторые ошибки указывают на «внутренний» инструмент, а не на сам g++.The command g++ main.cpp -o main isn't a single process — it's a coordinator (the driver) that launches a chain of separate programs. Understanding this chain explains what flags like -### (print the commands without running them) actually show, and why some errors point at an "internal" tool rather than at g++ itself.
g++/gcc — driverСам исполняемый файл g++ не компилирует код — он разбирает аргументы командной строки, определяет, какие языки и стадии нужны по расширениям файлов, и запускает нужные внутренние программы в правильном порядке с правильными аргументами. g++ -### main.cpp печатает весь план запуска, ничего реально не выполняя.The g++ executable itself doesn't compile code — it parses the command-line arguments, determines which languages and stages are needed based on file extensions, and launches the right internal programs in the right order with the right arguments. g++ -### main.cpp prints the whole execution plan without actually running anything.
cc1plus — собственно компилятор C++the actual C++ compilerОтдельный исполняемый файл (не виден пользователю напрямую, лежит внутри private-каталога установки GCC), который и выполняет все стадии из конвейера компиляции — препроцессор встроен внутрь cc1plus, а не запускается отдельным процессом, как можно было бы подумать по историческому флагу -E. На выходе — файл ассемблера (.s).A separate executable (not directly visible to the user, living inside GCC's private install directory) that runs all the stages of the compilation pipeline — the preprocessor is built into cc1plus itself, not spawned as a separate process, contrary to what the historical -E flag name might suggest. Its output is an assembly file (.s).
as — ассемблер (GNU Binutils)the assembler (GNU Binutils)Отдельная утилита не из состава GCC, а из пакета GNU Binutils — переводит текстовый файл ассемблера в машинный код и упаковывает его в объектный файл (.o). GCC полагается на системный as, а не несёт свой собственный ассемблер — отсюда практическая связка «версия GCC + версия Binutils» имеет значение для итогового бинарника.A separate utility not part of GCC itself, but from the GNU Binutils package — it turns the textual assembly file into machine code and packages it into an object file (.o). GCC relies on the system's as rather than bundling its own assembler — hence the practical "GCC version + Binutils version" combination matters for the resulting binary.
collect2 → ld — линковкаlinkingcollect2 — обёртка GCC над системным линковщиком (ld, снова из Binutils, либо lld/gold при явном выборе), нужная для дополнительной обработки, специфичной для C++: сбора статических конструкторов глобальных объектов из разных единиц трансляции в единый список, вызываемый до main (см. жизненный цикл процесса).collect2 is GCC's wrapper around the system linker (ld, again from Binutils, or lld/gold if explicitly selected), needed for extra C++-specific processing: collecting static global-object constructors from different translation units into a single list invoked before main (see the process lifecycle).
g++ -### main.cpp -o main 2>&1 | head -3
// печатает что-то вроде:// prints something like:
// /usr/libexec/gcc/x86_64-linux-gnu/13/cc1plus -quiet main.cpp -o /tmp/ccXXXXXX.s ...
// as /tmp/ccXXXXXX.s -o /tmp/ccYYYYYY.o
// /usr/libexec/gcc/x86_64-linux-gnu/13/collect2 ... /tmp/ccYYYYYY.o -lstdc++ -lc ...
cc1plus транслирует AST в GENERIC (языко-независимое дерево выражений, всё ещё довольно высокоуровневое), затем в GIMPLE — упрощённое трёхадресное представление (каждая инструкция — не более одной операции), над которым работает большинство скалярных и межпроцедурных оптимизаций (см. оптимизации). Финальный шаг — перевод в RTL (Register Transfer Language), уже близкое к машинным инструкциям представление, где работают планировщик команд (instruction scheduling) и распределение регистров (register allocation), специфичные для целевой архитектуры.cc1plus translates the AST into GENERIC (a language-independent expression tree, still fairly high-level), then into GIMPLE — a simplified three-address representation (each instruction is at most one operation), which is what most scalar and interprocedural optimizations operate on (see optimizations). The final step is lowering to RTL (Register Transfer Language), a representation already close to machine instructions, where instruction scheduling and register allocation — both target-architecture-specific — happen.
В отличие от LLVM IR у Clang, GIMPLE/RTL исторически не проектировались как стабильный, документированный публичный API — это одна из ключевых архитектурных причин, почему вокруг GCC не выросла такая же обширная экосистема сторонних инструментов, как вокруг LLVM.Unlike Clang's LLVM IR, GIMPLE/RTL were never historically designed as a stable, documented public API — this is one of the key architectural reasons why GCC never grew the same broad third-party tooling ecosystem that LLVM did.
С версии GCC 4.5 (2010) компилятор можно расширять плагинами — динамическими библиотеками, загружаемыми через -fplugin=path/to/plugin.so, которые подключаются к внутренним callback-точкам (PLUGIN_FINISH_TYPE, PLUGIN_PRE_GENERICIZE и другие) и получают доступ к внутренним структурам данных GCC — GENERIC-дереву, GIMPLE, таблице типов. В отличие от LibTooling у Clang, API плагинов GCC гораздо менее стабилен между версиями (internals меняются без обязательства сохранять обратную совместимость) и требует линковки против внутренних заголовков самого GCC.Since GCC 4.5 (2010), the compiler can be extended with plugins — dynamic libraries loaded via -fplugin=path/to/plugin.so — that hook into internal callback points (PLUGIN_FINISH_TYPE, PLUGIN_PRE_GENERICIZE, and others) and get access to GCC's internal data structures — the GENERIC tree, GIMPLE, the type table. Unlike Clang's LibTooling, GCC's plugin API is far less stable across versions (internals change with no backward-compatibility guarantee) and requires linking against GCC's own internal headers.
Практическое применение: Linux-ядро использует собственные GCC-плагины (scripts/gcc-plugins/) для дополнительных проверок безопасности во время сборки (structleak — обнуление структур с потенциально чувствительными данными, latent_entropy — добавление источника энтропии в точки входа ядра), которые невозможно выразить обычными предупреждениями компилятора.A practical application: the Linux kernel uses its own GCC plugins (scripts/gcc-plugins/) for extra build-time security checks (structleak — zeroing structs that may hold sensitive data, latent_entropy — adding an entropy source at kernel entry points) that can't be expressed through ordinary compiler warnings.
GCC поддерживает основные санитайзеры LLVM-происхождения — реализации независимые от Clang, но интерфейс флагов совпадает, что позволяет переносить команды сборки между компиляторами почти без изменений.GCC supports the main LLVM-originated sanitizers — implementations independent from Clang's, but with a matching flag interface, which lets build commands carry over between the two compilers almost unchanged.
| флагflag | ловитcatches | накладные расходыoverhead |
|---|---|---|
-fsanitize=address | переполнение буфера, use-after-free, double-freebuffer overflow, use-after-free, double-free | ~2x время, до 3x памятьtime, up to 3x memory |
-fsanitize=undefined | signed overflow, null-разыменование, неверный alignment, деление на 0signed overflow, null dereference, misalignment, division by zero | ~1.2x |
-fsanitize=thread | гонки данных (data races)data races | ~5-15x время, до 10x памятьtime, up to 10x memory |
-fsanitize=leak | утечки памяти (без ASan, отдельно)memory leaks (standalone, without ASan) | ~низкие на выходе из программыlow, incurred at program exit |
MemorySanitizer (обнаружение чтения неинициализированной памяти) в GCC отсутствует — единственный из основных санитайзеров LLVM-семейства, доступный только в Clang (см. профиль Clang), поскольку его реализация завязана на инструментацию, тесно интегрированную с внутренними проходами именно LLVM.MemorySanitizer (detecting reads of uninitialized memory) is absent from GCC — the one major sanitizer from the LLVM family available only in Clang (see the Clang profile), since its implementation depends on instrumentation tightly integrated with LLVM's own internal passes.
g++ -fsanitize=address,undefined -g -O1 main.cpp -o main
// -O1, а не -O0 — рекомендация самих санитайзеров: разумный компромисс
// между читаемостью стектрейса и реалистичной скоростью выполнения// -O1, not -O0 — the sanitizers' own recommendation: a reasonable trade-off
// between readable stack traces and realistic execution speed
Собственный синтаксис расширений языка, во многом позже принятый Clang для совместимости.GCC has its own extension syntax, much of it later adopted by Clang for compatibility.
__attribute__((...))void critical() __attribute__((always_inline)); // принудительный inline// force inlining
struct Packet { char a; int b; } __attribute__((packed)); // без выравнивания полей// no field padding
void fatal_error() __attribute__((noreturn)); // функция никогда не возвращается// function never returns
__builtin_expect — предшественник [[likely]]— the predecessor of [[likely]]Подсказка компилятору, какая ветка условия статистически более вероятна — используется в горячих путях ядра Linux повсеместно через макросы likely()/unlikely(), задолго до появления стандартных атрибутов [[likely]]/[[unlikely]] в C++20.A hint to the compiler about which branch of a condition is statistically more likely — used pervasively on Linux kernel hot paths through the likely()/unlikely() macros, long before the standard [[likely]]/[[unlikely]] attributes arrived in C++20.
#define likely(x) __builtin_expect(!!(x), 1)
if (likely(ptr != nullptr)) { /* ... */ }
Нестандартное расширение: блок кода в фигурных скобках как выражение, возвращающее значение последнего оператора — синтаксис ({ ... }), повсеместно используемый в заголовках ядра Linux для безопасных типизированных макросов (классический пример — макрос max(), вычисляющий каждый аргумент ровно один раз).A non-standard extension: a braced block of code as an expression, evaluating to the value of its last statement — the ({ ... }) syntax, used pervasively in Linux kernel headers for type-safe macros (the classic example being a max() macro that evaluates each argument exactly once).
#define max(a, b) ({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; })
| флагflag | что делаетwhat it does |
|---|---|
-O0 | без оптимизаций (по умолчанию) — код 1:1 соответствует исходнику, лучшая отладкаno optimizations (the default) — code matches source 1:1, best debugging |
-O1 | базовые безопасные оптимизации, не требующие компромисса время-компиляции/размер — рекомендуемый уровень для сборок с санитайзерамиbasic safe optimizations that don't trade off compile time/size — the recommended level for sanitizer builds |
-O2 | большинство безопасных по времени/размеру оптимизаций (inlining, devirtualization, vectorization в разумных пределах) — стандарт для продакшн-сборокmost optimizations safe on both time and size (inlining, devirtualization, vectorization within reason) — the standard for production builds |
-O3 | + агрессивная автовекторизация, loop unrolling, function cloning — иногда крупнее бинарник, не всегда быстрее на практике+ aggressive auto-vectorization, loop unrolling, function cloning — sometimes a larger binary, not always faster in practice |
-Ofast | -O3 + нарушающие строгое соответствие стандарту оптимизации (-ffast-math) — переупорядочивает floating-point операции, ломает errno/NaN-семантику, требует осознанного согласия на неточность-O3 plus optimizations that break strict standard conformance (-ffast-math) — reorders floating-point operations, breaks errno/NaN semantics, requires deliberately accepting reduced precision |
-Os | оптимизация под размер кода, а не скорость — критично для embedded с единицами килобайт flashoptimizing for code size rather than speed — critical for embedded targets with just kilobytes of flash |
-Og | лёгкие оптимизации, специально не мешающие пошаговой отладке — компромисс между -O0 и -O1 для повседневной разработкиlight optimizations specifically designed not to interfere with step-through debugging — a compromise between -O0 and -O1 for everyday development |
-march=native | генерация кода под конкретный набор инструкций текущего CPU (AVX2/AVX-512 и т.д.) — бинарник может не запуститься на другом процессореgenerate code for the exact instruction set of the current CPU (AVX2/AVX-512, etc.) — the binary may fail to run on a different processor |
-flto | Link-Time Optimization — оптимизации через границы единиц трансляции (см. оптимизации)Link-Time Optimization — optimizations across translation-unit boundaries (see optimizations) |
-fprofile-generate / -fprofile-use | PGO — двухпроходная профиль-управляемая оптимизацияtwo-pass profile-guided optimization |
ld (линковщик), as (ассемблер), objdump (дизассемблирование и анализ секций), nm (таблица символов), readelf (структура ELF-файла) — стандартная связка для линковки и анализа бинарников на Linux, используемая GCC по умолчанию.ld (linker), as (assembler), objdump (disassembly and section analysis), nm (symbol table), readelf (ELF file structure) — the standard toolkit for linking and inspecting binaries on Linux, used by GCC by default.
gcov — встроенный инструмент покрытия кода тестами, требует сборки с --coverage; lcov — надстройка, агрегирующая данные gcov в наглядные HTML-отчёты с построчной подсветкой покрытых/непокрытых строк.gcov is a built-in test-coverage tool, requiring a build with --coverage; lcov is a layer on top that aggregates gcov data into readable HTML reports with line-by-line highlighting of covered/uncovered code.
Исторически теснее всего интегрирован именно с отладочной информацией, генерируемой GCC (формат DWARF) — хотя современный gdb прекрасно работает и с бинарниками Clang.Historically the most tightly integrated with the debug information GCC generates (the DWARF format) — though a modern gdb works perfectly well with Clang-built binaries too.
плюсы:pros: максимальное число поддерживаемых целевых архитектур из всех трёх (см. обзор других компиляторов и таргетов), зрелые backend-оптимизации, накопленные за десятилетия, стандарт де-факто для встраиваемых систем и ядра Linux, полностью свободная лицензия.the widest range of supported target architectures of all three (see the overview of other compilers and targets), mature backend optimizations accumulated over decades, the de-facto standard for embedded systems and the Linux kernel, a fully free license.
минусы:cons: исторически менее читаемая диагностика ошибок (хотя разрыв с Clang в последних версиях заметно сократился), внутренние API (GIMPLE/RTL, plugin-интерфейс) не рассчитаны на стабильное переиспользование сторонними инструментами, GPL создаёт трение для встраивания компилятора внутрь проприетарных продуктов, отсутствие MemorySanitizer.historically less readable error diagnostics (though the gap with Clang has narrowed considerably in recent versions), internal APIs (GIMPLE/RTL, the plugin interface) aren't designed for stable reuse by third-party tools, GPL creates friction for embedding the compiler inside proprietary products, no MemorySanitizer.