Чем пишут программы, как текст становится исполняемым файлом и что делать, когда сборка не прошла
Сайт на PHP или Python кажется далёким от C. Но под ним почти всё написано на C и C++:
| Программа | Что делает для сайта | Язык |
|---|---|---|
| Linux, ядро Windows | операционная система сервера | C |
| nginx, Apache | принимают HTTP-запросы | C |
| PostgreSQL, MySQL, SQLite, Redis | хранят данные | C, C++ |
| интерпретаторы PHP и Python (CPython) | выполняют код сайта | C |
| Chrome, Firefox, движок JavaScript V8 | показывают страницу и выполняют её скрипты | C++ |
| OpenSSL, zlib, libpng | шифруют HTTPS, сжимают ответы, читают картинки | C |
C — язык, на котором видно, как программа работает с памятью: где лежит число, сколько байт занимает строка, кто и когда освобождает память. Поэтому он и выбран для курса алгоритмов: сортировка, список или хеш-таблица на C пишутся целиком, без готовых «чёрных ящиков». Когда устройство понятно, курс переходит к C++ (раздел 20): там те же структуры данных уже есть в стандартной библиотеке, и на нём пишутся оконные и игровые приложения (разделы 25–28).
.c, компилятор gcc.Процессор выполняет машинные команды — числа в памяти. Программа — текст. Перевести текст в команды можно двумя способами.
Компилятор переводит текст программы целиком, заранее, один раз — и записывает результат в исполняемый файл (.exe в Windows, файл без расширения в Linux). Запускается потом только этот файл; ни исходный текст, ни компилятор для запуска не нужны. Языки, программы на которых обычно компилируют: C, C++, Go, Rust.
Интерпретатор читает текст программы при каждом запуске и сразу выполняет его. Без интерпретатора программа не запустится. Так работают Python, PHP, JavaScript в браузере.
| Компиляция (C) | Интерпретация (Python, PHP) | |
|---|---|---|
| Когда находится опечатка в имени переменной | при сборке, до первого запуска | когда выполнение дойдёт до этой строки — может быть, через месяц работы сайта |
| Что нужно на компьютере пользователя | только исполняемый файл (для своей ОС и процессора) | интерпретатор нужной версии и исходный текст |
| Скорость | машинный код — быстро | медленнее в разы и в десятки раз на циклах |
| Цикл «правка — проверка» | правка → сборка → запуск | правка → запуск |
Граница размыта: Java и C# компилируются в промежуточный код, который выполняет виртуальная машина; JavaScript в браузере сначала интерпретируется, а часто выполняемые функции компилируются прямо во время работы. Но для C всё просто: исходный текст → компилятор → исполняемый файл.
Язык C описан международным стандартом ISO/IEC 9899. Стандарт говорит, что значит каждая конструкция языка и что обязана уметь стандартная библиотека. Компилятор, который выполняет стандарт, соберёт одну и ту же программу одинаково в Windows, Linux и на микроконтроллере.
| Стандарт | Год | Что появилось (примеры) |
|---|---|---|
| C89 / C90 | 1989–1990 | первый стандарт: язык в том виде, в каком его описали Керниган и Ритчи |
| C99 | 1999 | комментарии //, тип long long, bool из <stdbool.h>, объявление переменной в любом месте блока и в заголовке for |
| C11 | 2011 | _Static_assert, анонимные структуры, поддержка Юникода в строках; стандарт курса |
| C17 | 2018 | исправления C11 без новых возможностей |
| C23 | 2024 | bool, true, false и nullptr — ключевые слова, typeof, двоичные литералы 0b1010 |
У C++ свой стандарт (ISO/IEC 14882) и свои версии: C++98, C++11, C++14, C++17 (стандарт курса с раздела 20), C++20, C++23. Стандарт выбирается ключом компилятора: -std=c11 для C, -std=c++17 для C++.
Для работы нужны три программы. Редактор — в нём пишут текст программы. Компилятор — собирает программу. Отладчик — выполняет программу по шагам и показывает значения переменных. Интегрированная среда разработки (IDE — Qt Creator, CLion, Visual Studio) объединяет их в одном окне, но внутри у неё те же компилятор и отладчик. В курсе используется компилятор gcc (GNU Compiler Collection) и отладчик gdb; редактор — любой, например VS Code или Notepad++.
Windows. gcc для Windows называется MinGW-w64. Удобнее всего ставить его через MSYS2:
msys2.org и установить в C:\msys64 (путь без пробелов и кириллицы).pacman -Syu
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gdb make
Path папку C:\msys64\ucrt64\bin — тогда gcc запускается из любой консоли Windows, а не только из терминала MSYS2.Linux (Debian, Ubuntu):
sudo apt install gcc gdb make
Проверка — в новом окне консоли:
gcc --version
gdb --version
Обе команды должны вывести номер версии (gcc 13 и новее). Сообщение "gcc" не является внутренней или внешней командой (или gcc: command not found в Linux) значит, что компилятор не установлен или папка не добавлена в Path; после правки Path консоль нужно открыть заново.
Создайте папку для курса без пробелов и кириллицы в пути (например, C:\alg\01), в ней — файл hello.c:
/* Первая программа на C */
#include <stdio.h>
int main(void)
{
printf("Hello, world!\n");
return 0;
}
#include <stdio.h> — подключить заголовочный файл стандартной библиотеки ввода-вывода: в нём объявлена функция printf.int main(void) — функция main, с которой начинается выполнение программы. void в скобках означает «без параметров». int перед именем — функция возвращает целое число.printf("Hello, world!\n"); — вывести строку. \n — перевод строки. Каждая простая инструкция заканчивается точкой с запятой.return 0; — завершить программу с кодом возврата 0, «всё в порядке». Ненулевой код означает ошибку; его видит тот, кто запустил программу (консоль, скрипт, другая программа).Сборка и запуск — в консоли, в папке с файлом:
gcc -std=c11 -Wall -Wextra -pedantic hello.c -o hello
hello
Hello, world!
| Часть команды | Что значит |
|---|---|
-std=c11 | собирать по стандарту C11 |
-Wall -Wextra | включить предупреждения о подозрительном коде (по умолчанию большая часть выключена) |
-pedantic | предупреждать обо всём, что не входит в выбранный стандарт: расширениях gcc, которые другой компилятор не примет |
hello.c | исходный файл |
-o hello | имя результата: hello.exe в Windows (расширение gcc добавит сам), hello в Linux |
В Linux и в терминале MSYS2 программа из текущей папки запускается как ./hello — без ./ оболочка ищет команду только в папках из PATH. В cmd и PowerShell Windows — hello или .\hello.
Код возврата последней программы можно посмотреть: echo $? в Linux и MSYS2, echo %errorlevel% в cmd, $LASTEXITCODE в PowerShell. Для hello это 0.
gcc -std=c11 -Wall -Wextra -pedantic без единого предупреждения. Программа с предупреждениями на проверку не принимается: почти каждое предупреждение — ошибка, которая ещё не проявилась (пункт 07).Одна команда gcc на самом деле запускает по очереди четыре программы:
hello.c ──► препроцессор ──► hello.i ──► компилятор ──► hello.s
исходный #include, текст C → ассемблер
текст #define без # ассемблер
hello.s ──► ассемблер ──► hello.o ──► компоновщик ──► hello.exe
объектный + код библиотек исполняемый
файл (printf и др.) файл
| Этап | Что делает | Остановиться на нём |
|---|---|---|
| Препроцессор | выполняет строки, начинающиеся с #: вставляет вместо #include текст заголовочного файла, подставляет макросы, убирает комментарии | gcc -E hello.c -o hello.i |
| Компилятор | проверяет, что текст — правильная программа на C, и переводит её на язык ассемблера | gcc -S hello.c → hello.s |
| Ассемблер | переводит ассемблер в машинный код — объектный файл | gcc -c hello.c → hello.o |
Компоновщик (линкер, ld) | соединяет объектные файлы программы с кодом библиотек (там лежит сама printf) и оформляет исполняемый файл для ОС | gcc hello.o -o hello |
Откройте hello.i в редакторе: в нём сотни строк — это содержимое stdio.h и заголовков, которые подключает он сам, а ваша функция main — в самом конце. В stdio.h только объявление printf — её имя, параметры и тип результата. Код функции уже скомпилирован и лежит в библиотеке; его подставляет компоновщик.
Зачем знать этапы: по сообщению об ошибке видно, на каком этапе она случилась, а значит — где искать.
| Вид сообщения | Этап | Где искать причину |
|---|---|---|
fatal error: stdoi.h: No such file or directory | препроцессор | имя заголовочного файла в #include |
файл.c:5:22: error: … — с номером строки и столбца | компилятор | указанная строка или строка над ней |
undefined reference to …, ld returned 1 exit status | компоновщик | функция объявлена или вызвана, но её кода нет: опечатка в имени, не подключена библиотека |
Сообщение компилятора имеет вид файл:строка:столбец: вид: текст. error — программа написана не по правилам языка, исполняемый файл не создан. warning — по правилам, но подозрительно; исполняемый файл создан. Под сообщением gcc печатает саму строку и знак ^ под местом ошибки.
Пропущена точка с запятой.
#include <stdio.h>
int main(void)
{
printf("Hello\n")
return 0;
}
semicolon.c: In function 'main':
semicolon.c:5:22: error: expected ';' before 'return'
5 | printf("Hello\n")
| ^
| ;
6 | return 0;
| ~~~~~~
Компилятор заметил ошибку, только увидев следующее слово return, но указывает место точно: строка 5, после закрывающей скобки — и даже подсказывает, что вставить. Когда ошибка в конце строки, сообщение часто указывает на начало следующей: смотрите и на строку выше.
Опечатка в имени.
int count = 5;
printf("%d\n", cout);
undeclared.c:6:20: error: 'cout' undeclared (first use in this function); did you mean 'count'?
Забыт #include. Без <stdio.h> компилятор не знает, что такое printf. С версии 14 gcc считает это ошибкой (раньше было предупреждение):
noinclude.c:3:5: error: implicit declaration of function 'printf' [-Wimplicit-function-declaration]
noinclude.c:1:1: note: include '<stdio.h>' or provide a declaration of 'printf'
Ошибка компоновщика. Опечатка в имени main — int mian(void). Компилятор доволен: функцию mian написать можно. Но компоновщик не находит, с чего начинать программу:
Linux: undefined reference to `main'
Windows: undefined reference to `WinMain'
collect2: error: ld returned 1 exit status
Номера строки нет — компоновщик работает с объектными файлами, а не с текстом. WinMain в Windows — потому что, не найдя main, библиотека пробует точку входа оконного приложения.
Предупреждение, которое нельзя пропускать. %d в printf означает «вывести целое», а передано вещественное число:
#include <stdio.h>
int main(void)
{
printf("%d\n", 3.5);
return 0;
}
format.c:5:14: warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' [-Wformat=]
Exe создан, и он выводит не 3 и не 3.5, а случайное число — разное на разных компьютерах и запусках. Предупреждение в квадратных скобках называет ключ, который его включил: -Wformat= входит в -Wall. Без -Wall предупреждения не было бы, а ошибка осталась бы.
Программа собралась без предупреждений, но выводит не то. Отладчик позволяет остановить её на нужной строке, выполнять дальше по одной строке и смотреть значения переменных. Программа считает сумму 1 + 2 + … + n и ошибается:
/* Сумма 1 + 2 + ... + n — с ошибкой, для отладчика */
#include <stdio.h>
int main(void)
{
int n = 4;
int sum = 0;
for (int i = 1; i < n; i++) {
sum = sum + i;
}
printf("sum = %d\n", sum);
return 0;
}
sum = 6
Ожидалось 10. Цикл for будет в разделе 6; сейчас достаточно знать, что строка 9 выполняется несколько раз. Для отладки программа собирается с ключом -g — в exe записываются номера строк и имена переменных — и запускается под gdb:
gcc -std=c11 -Wall -Wextra -pedantic -g debugsum.c -o debugsum
gdb debugsum
(gdb) break 9 остановиться перед строкой 9
Breakpoint 1 at ...: file debugsum.c, line 9.
(gdb) run запустить программу
Breakpoint 1, main () at debugsum.c:9
9 sum = sum + i;
(gdb) print i значение переменной
$1 = 1
(gdb) continue выполнять до следующей остановки
(gdb) print i
$2 = 2
(gdb) continue
(gdb) print i
$3 = 3
(gdb) continue
sum = 6 остановок с i = 4 не было
[Inferior 1 (process ...) exited normally]
(gdb) quit
Вывод gdb здесь сокращён: многоточия и строки после continue убраны. Видно главное: при i = 4 тело не выполнилось — условие i < n не пускает последнее слагаемое. Исправление: i <= n.
| Команда | Сокращение | Что делает |
|---|---|---|
break 9, break main | b | точка останова на строке или в начале функции |
run | r | запустить программу |
next | n | выполнить текущую строку и остановиться на следующей |
print sum | p | показать значение переменной или выражения |
display sum | показывать значение после каждого шага | |
continue | c | выполнять до следующей точки останова |
quit | q | выйти из отладчика |
-stdgcc -std=c11 -Wall -Wextra -pedantic файл.c -o файл, без единого предупреждения-g, поставить точку останова, смотреть переменные