Компиляция в программировании — перевод программы с языка, на котором её написал человек, в машинный код, который выполняет процессор. Переводит специальная программа, компилятор: на входе текстовый файл с кодом, на выходе исполняемый файл, который запускается без исходников. Чтобы выбрать и поставить компилятор для языка Си, есть отдельная инструкция: компилятор C для Windows, macOS и Linux.
Ниже разберём, что значит «скомпилировать» простыми словами, и пройдём все этапы компиляции на живой программе из девяти строк. Вывод в примерах настоящий: мы запускали команды на сервере с Ubuntu 24.04 и компилятором GCC 13.3.
У слова есть и второе значение. В литературе и музыке компиляцией называют сборник из чужих материалов или работу, составленную из заимствований, отсюда «компилятивный текст» (значение есть в Большом толковом словаре на «Грамоте»). Дальше речь только о коде.
Что такое компиляция простыми словами

Процессор понимает только числа: каждая команда закодирована последовательностью байтов. Писать так вручную долго и легко ошибиться, поэтому люди пишут на Си, C++, Go или Java, а в машинные команды текст переводит компилятор.
Удобная аналогия: перевод книги. Переводчик один раз переводит роман целиком, и дальше книгу читают на новом языке без него. Компилятор работает так же: перевёл программу заранее, и готовый файл запускается сколько угодно раз.
Что участвует в процессе:
- исходный код: текст программы, например файл
hello.c; - компилятор, программа-переводчик: GCC, Clang, MSVC в Visual Studio,
javacдля Java; - объектный файл: переведённый кусок программы, который ещё не умеет запускаться;
- исполняемый файл, то есть готовая программа:
.exeв Windows, файл без расширения в Linux и macOS.
Скомпилировать программу значит прогнать её через всю эту цепочку и получить исполняемый файл. Если вы только начинаете писать код, общая картина профессии есть в материале программирование с нуля.
Этапы компиляции на примере программы на C
В документации GCC прямо сказано, что сборка может включать до четырёх стадий: препроцессинг, собственно компиляцию, ассемблирование и компоновку, всегда в этом порядке (раздел Overall Options). Обычно все четыре проходят одной командой, но каждую можно остановить флагом и посмотреть промежуточный результат.
Возьмём программу hello.c:
#include <stdio.h>
#define GREETING "Привет, компилятор!"
int main(void) {
int answer = 42;
printf("%s Ответ: %d\n", GREETING, answer);
return 0;
}
Препроцессинг: 9 строк превращаются в 822
Препроцессор работает только с текстом программы: подставляет содержимое подключённых файлов и заменяет макросы на их значения. Остановиться после этого шага помогает флаг -E:
$ gcc -E hello.c -o hello.i
$ wc -l hello.i
822 hello.i
Из девяти строк получилось 822: почти всё это содержимое заголовка stdio.h. В конце файла лежит наша функция, где вместо GREETING уже стоит сама строка:
int main(void) {
int answer = 42;
printf("%s Ответ: %d\n", "Привет, компилятор!", answer);
return 0;
}
Компиляция в ассемблер
Это и есть компиляция в узком смысле: компилятор разбирает код, проверяет его и переводит в команды конкретного процессора, записанные текстом. Флаг -S останавливает сборку на этом шаге и сохраняет файл hello.s. Фрагмент функции main для процессора x86-64:
main:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
movl $42, -4(%rbp)
...
call printf@PLT
movl $0, %eax
leave
ret
Наша переменная видна в строке movl $42, -4(%rbp): число 42 кладётся в память. Для процессора ARM, например в Mac на чипах Apple, ассемблер будет другим, поэтому программу на Си собирают отдельно под каждую платформу. Сам язык, на котором написан пример, можно изучить на курсах по языку C.
Ассемблирование: объектный файл
Ассемблер переводит текстовые команды в байты и пишет объектный файл. Флаг -c останавливает сборку здесь:
$ gcc -c hello.c
$ file hello.o
hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ nm hello.o
0000000000000000 T main
U printf
Самое интересное в выводе nm. Буква T значит, что функция main в файле есть. Буква U рядом с printf значит «используется, но не найдена»: кода этой функции в объектном файле нет, он лежит в стандартной библиотеке.
Компоновка (линковка)
Компоновщик, он же линкер, собирает объектные файлы и библиотеки в одну программу и связывает вызовы с адресами функций. После него printf находится, и файл запускается:
$ gcc hello.o -o hello
$ ./hello
Привет, компилятор! Ответ: 42
Если имя выходного файла не задать флагом -o, GCC назовёт программу a.out. Вот что получилось на каждом шаге:
| Этап | Команда | Файл | Размер |
|---|---|---|---|
| Исходный код | нет | hello.c | 183 байта |
| Препроцессинг | gcc -E | hello.i | 21 390 байт |
| Компиляция | gcc -S | hello.s | 990 байт |
| Ассемблирование | gcc -c | hello.o | 1 600 байт |
| Компоновка | gcc hello.o -o hello | hello | 15 960 байт |
Исполняемый файл в 87 раз больше исходника, хотя кода printf в нём нет: функция подтягивается из системной библиотеки при запуске. Объём занимают служебные заголовки формата ELF и таблицы для загрузчика.
В других языках компоновщик ведёт себя иначе. Go по умолчанию кладёт в исполняемый файл и свою среду выполнения, поэтому программа запускается на сервере без установленного Go. Путь в эту специальность разобран в статье как стать Go-разработчиком.
Канал основателя Checkroi Вани Буявца3 700 человек читают мой Телеграм-канал про нейросетиСобрал промпты для Claude Code и ChatGPT, разборы ИИ-инструментов и лайфхаки по продвижению бизнеса в одном местеПрисоединитьсяЧто происходит внутри компилятора

Шаг «компиляция» из схемы выше сам состоит из нескольких фаз. Это классическая схема из теории компиляции, и по ней удобно понимать, откуда берутся сообщения об ошибках. Знать её полезно любому программисту, даже если сам компилятор вы никогда писать не будете.
| Фаза | Что делает | Что ловит |
|---|---|---|
| Лексический анализ | Режет текст на токены: int, answer, =, 42, ; |
Недопустимые символы |
| Синтаксический анализ | Строит дерево разбора по правилам языка | Пропущенную точку с запятой, лишнюю скобку |
| Семантический анализ | Проверяет смысл: типы, объявления, области видимости | Сложение строки с числом, необъявленную переменную |
| Промежуточный код | Переводит дерево в простые команды, например трёхадресный код вида t1 = a + b |
ничего, код уже проверен |
| Оптимизация | Выбрасывает лишнее, считает константы заранее, упрощает циклы | ничего |
| Генерация кода | Выдаёт ассемблер под нужный процессор | ничего |
Оптимизацию включают флагами -O1, -O2, -O3. По умолчанию GCC собирает с -O0: так быстрее компилировать и проще отлаживать.
Отдельно стоит кросс-компиляция, когда программу собирают на одном компьютере для другого процессора. Так работает, например, программист микроконтроллеров: код пишет на ноутбуке, а прошивку собирает для чипа в устройстве.
Компиляция, интерпретация и JIT
Компилируют не все языки одинаково. Интерпретатор работает как синхронный переводчик: читает программу и выполняет её на ходу, отдельного исполняемого файла не появляется. Между этими полюсами есть промежуточные схемы.
| Подход | Как работает | Примеры |
|---|---|---|
| Компиляция в машинный код | Заранее, целиком, под конкретную платформу | C, C++, Go, Rust |
| Компиляция в байт-код и JIT | Компилятор делает байт-код, виртуальная машина при запуске докомпилирует частые участки в машинный код | Java, Kotlin, C# |
| Байт-код и интерпретатор | Код незаметно компилируется в байт-код, который исполняет виртуальная машина | Python, Ruby |
Java и Kotlin
По документации Oracle, javac читает файлы .java и компилирует их в файлы .class, которые выполняет виртуальная машина Java (справка по javac).
Один и тот же .class запускается на Windows, Linux и macOS, если там стоит JVM. На той же машине работает Kotlin, а путь в профессию разобран в статье как стать Java-разработчиком.
Python тоже компилирует
Python называют интерпретируемым, но перед выполнением он переводит функцию в байт-код. Его показывает стандартный модуль dis. Мы разобрали одну и ту же функцию в двух версиях Python:
def greet(name):
return "Привет, " + name
# Python 3.12
LOAD_CONST 1 ('Привет, ')
LOAD_FAST 0 (name)
BINARY_OP 0 (+)
RETURN_VALUE
# Python 3.14
LOAD_CONST 0 ('Привет, ')
LOAD_FAST_BORROW 0 (name)
BINARY_OP 0 (+)
RETURN_VALUE
Команды отличаются, и это нормально. В документации модуля dis сказано, что байт-код считается внутренней деталью CPython и между версиями его могут менять. Поэтому в папке __pycache__ у каждого файла с байт-кодом в имени стоит версия, например greet.cpython-312.pyc. Скачать сам Python лучше с официального сайта, как в инструкции про python.org.
Ошибки компиляции и линковки: как их читать

Новички часто путают два вида ошибок. Компилятор ругается на текст программы, линкер ругается на то, что не нашёл нужную функцию. Ниже настоящие сообщения GCC 13.3 и что с ними делать.
Пропущена точка с запятой
err1.c:4:23: error: expected ';' before 'return'
4 | printf("Привет\n")
| ^
| ;
Числа 4:23 означают строку и столбец. GCC даже показывает, куда вставить символ.
Решение: поставить ; в конце строки 4. Ошибка всплывает на синтаксическом анализе, объектный файл не создаётся.
Функция объявлена, но не написана
/usr/bin/ld: /tmp/ccwqztyZ.o: in function `main':
err2.c:(.text+0xe): undefined reference to `square'
collect2: error: ld returned 1 exit status
Сообщение пишет уже ld, то есть линкер. С флагом -c тот же файл компилируется без ошибок, а nm показывает U square: компилятор поверил объявлению функции, но кода её нет ни в одном файле.
Решение: дописать функцию или добавить в команду файл, где она лежит, например gcc main.c square.c -o app. Если функция из внешней библиотеки, библиотеку подключают флагом, скажем -lm для математики.
Забыли подключить заголовок
err3.c:2:5: warning: implicit declaration of function 'printf'
err3.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf'
В GCC 13 это предупреждение, и программа соберётся. Начиная с GCC 14 вызов необъявленной функции стал ошибкой, сборка остановится (заметки GCC 14 о переходе).
Решение: добавить #include <stdio.h> первой строкой. Полезно сразу собирать с флагом -Wall, чтобы видеть все предупреждения.
Правило для разбора любой ошибки: читайте первое сообщение сверху. Остальные часто его следствие и исчезают сами, когда первое исправлено.
Где освоить программирование системно
Чтобы понять компиляцию, хватит одного вечера с терминалом. Чтобы писать программы, которые собираются без ошибок и работают быстро, нужна практика с наставником и код-ревью. Курсы по C++ собраны в разделе курсы C++, по Java — в Java-разработке, ниже рейтинг программ по программированию в целом.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Нейросети для рабочих задач Перейти на сайт курса | 31 290 ₽ | - | 1 месяц | Обзор курса | |
| Нейросети. Практический курс Перейти на сайт курса | 74 900 ₽ | - | 3 месяца | Обзор курса | |
| Frontend-разработчик с нуля Перейти на сайт курса | 113 400 ₽ | 5385 ₽/мес. | 10 месяцев | Обзор курса | |
| Fullstack-разработчик на Python Перейти на сайт курса | 161 200 ₽ | 7125 ₽/мес. | 21 месяц | Обзор курса | |
| Профессия «Python-разработчик» Перейти на сайт курса | 157 335 ₽ | 5987 ₽/мес. | 10 месяцев | Обзор курса |
Больше программ — в полном каталоге курсов по программированию




