Menu

Как скомпилировать и запустить программу на C (gcc, шаг за шагом)

Превращаем файл .c в работающую программу: команда gcc, что на самом деле делают препроцессирование, компиляция и компоновка, флаги, которые стоит использовать с первого дня, и как читать ошибки, когда всё ломается.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

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

Две команды

Сохраните это как hello.c:

#include <stdio.h>

int main(void) {
    printf("Привет, C!\n");
    return 0;
}

Затем в той же папке:

gcc hello.c -o hello
./hello

Первая команда компилирует. Вторая запускает результат. В Windows команда запуска — hello в Command Prompt или .\hello в PowerShell: префикса ./ там нет, потому что Windows по умолчанию ищет в текущем каталоге, а Unix-оболочки намеренно этого не делают.

Если опустить -o hello, gcc назовёт результат a.out (или a.exe) — поэтому старые руководства заканчиваются на ./a.out. Всегда задавайте имя выходного файла: это стоит четырёх символов и избавляет от путаницы.

Вот та же программа в браузерном редакторе, который делает оба шага за вас:

Что на самом деле делает «компиляция»

gcc hello.c -o hello выглядит как один шаг. На деле их четыре, и каждый может провалиться со своим особым видом ошибки.

1. Препроцессирование. Прежде чем будет скомпилирован хоть один символ C, препроцессор обрабатывает каждую строку, начинающуюся с #. #include <stdio.h> буквально заменяется содержимым этого заголовочного файла; макросы #define раскрываются; блоки #ifdef сохраняются или удаляются. На выходе — один большой файл C, в котором не осталось строк с #. Его можно посмотреть:

gcc -E hello.c

Это выведет сотни строк — почти всё это вставленный stdio.h.

2. Компиляция. Препроцессированный C разбирается, проверяется по типам и переводится в ассемблер для вашего процессора. Отсюда берутся синтаксические ошибки, ошибки типов и предупреждения.

3. Ассемблирование. Ассемблер превращается в объектный файл — машинный код с заглушками там, где находятся вызовы функций из других файлов.

gcc -c hello.c   # создаёт hello.o и останавливается перед компоновкой

4. Компоновка. Объектные файлы сшиваются вместе со стандартной библиотекой C, каждая заглушка заполняется настоящим адресом, и получается исполняемый файл. Отсюда берутся ошибки «undefined reference».

Практический вывод: ошибка компиляции указывает на строку в вашем исходнике, а ошибка компоновки — нет, потому что компоновка происходит после того, как все строки уже приняты.

Флаги, которые стоит использовать с первого дня

Голая команда gcc file.c -o prog молча принимает немало опасного кода. Четыре флага это меняют.

gcc -std=c17 -Wall -Wextra -g hello.c -o hello
  • -Wall включает распространённые предупреждения. Вопреки названию, это не «все» предупреждения, а разумный набор.
  • -Wextra добавляет ещё, в том числе про неиспользуемые параметры и некоторые ошибки в сравнениях.
  • -std=c17 фиксирует стандарт языка, чтобы ваш код означал одно и то же на любой машине. Используйте -std=c99, если следуете более старым материалам.
  • -g сохраняет отладочную информацию, чтобы gdb или lldb могли показать реальные строки вашего исходника, когда что-то падает.

Ещё два, которые понадобятся со временем:

  • -O2 включает оптимизацию для релизных сборок. Во время учёбы держите его выключенным: оптимизированный код труднее отлаживать, а предупреждения иногда смещаются.
  • -fsanitize=address,undefined (gcc и clang) заставляет программу аварийно завершиться с внятным сообщением в тот самый момент, когда она читает за границами или попадает в неопределённое поведение. Это самый полезный учебный флаг в C.

Проверьте предупреждения сами. Эта программа компилируется и запускается, но в ней два настоящих бага:

Переменной count никогда не присваивается значение, поэтому программа печатает те байты, что случайно там оказались. С -Wall компилятор так и говорит: 'count' is used uninitialized. Предупреждения в C почти никогда не бывают шумом — относитесь к ним как к ошибкам, до которых вы ещё не дошли.

Компиляция нескольких файлов

Настоящие программы разбиты на файлы. Передайте их gcc все сразу:

gcc -std=c17 -Wall main.c utils.c -o myprog

Или скомпилируйте каждый по отдельности и скомпонуйте в конце — именно так поступают системы сборки, чтобы изменение одного файла не пересобирало всё:

gcc -c main.c      # -> main.o
gcc -c utils.c     # -> utils.o
gcc main.o utils.o -o myprog

Некоторым библиотекам нужен явный флаг компоновки. Математическая библиотека — та, с которой сталкивается каждый новичок:

gcc calc.c -o calc -lm

Без -lm использование sqrt из math.h прекрасно компилируется, а затем падает на этапе компоновки с undefined reference to sqrt: объявление было в заголовочном файле, но код лежал в библиотеке, которую никто не попросил.

Читаем ошибки

Ошибки C лаконичны, но последовательны. Три примера покрывают большую часть того, что вы увидите на первых порах.

Пропущенная точка с запятой сообщается на следующей строке, потому что компилятор продолжил чтение:

hello.c:5:5: error: expected ';' before 'return'

Исправлять нужно строку 4, а не 5. Всякий раз, когда ошибка указывает на строку, которая выглядит нормально, проверьте строку выше.

Отсутствующий заголовочный файл выглядит как загадка про функцию, имя которой вы явно написали правильно:

hello.c:4:5: warning: implicit declaration of function 'printf'

Это значит, что компилятор не видел объявления printf, то есть вы забыли #include <stdio.h>. В C99 и позже это ошибка, а не просто предупреждение.

Сбой компоновки вообще не содержит номера строки:

/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'

Компилятор поверил вам, что helper где-то существует; компоновщик поискал — и не нашёл. Либо вы так и не написали определение, либо написали имя иначе, либо забыли передать gcc файл, в котором оно лежит.

Всегда исправляйте первую ошибку. Ошибки C идут лавиной: одна плохая строка может породить двадцать сообщений, и девятнадцать из них исчезнут, как только будет исправлена первая.

Коды возврата

return 0 из main — не украшение. Это код завершения программы, и оболочка может его прочитать:

После запуска echo $? на macOS/Linux (или echo %errorlevel% в Windows) выведет это число. Скрипты и инструменты сборки по нему решают, продолжать ли. Соглашение абсолютно: 0 — успех, ненулевое значение — код ошибки на ваш выбор. В stdlib.h определены EXIT_SUCCESS и EXIT_FAILURE, если вам приятнее имена.

Начиная с C99 достижение конца main без return трактуется как return 0, но писать его явно понятнее, а для всех остальных функций это обязательно.

Часто задаваемые вопросы

Как скомпилировать и запустить программу на C?

Сохраните код как hello.c, затем выполните gcc hello.c -o hello, чтобы собрать программу, и ./hello, чтобы её запустить (в Windows просто hello). Если gcc не найден, сначала нужно установить компилятор.

Что делает gcc -o?

-o задаёт имя выходного файла. gcc hello.c -o hello создаёт исполняемый файл с именем hello. Без -o gcc пишет в a.out (или a.exe в Windows) — именно поэтому во множестве старых руководств запускают ./a.out.

Какие флаги gcc стоит использовать всегда?

gcc -std=c17 -Wall -Wextra -g yourfile.c -o yourprog. -Wall -Wextra включают предупреждения, которые ловят настоящие баги, -std=c17 фиксирует версию языка, а -g сохраняет отладочные символы, чтобы отладчик мог показать ваш исходник. Добавьте -O2, когда нужна скорость в релизной сборке.

Что означает «undefined reference to» в C?

Это ошибка компоновщика: компилятор принял вызов функции, но определения этой функции нигде не нашлось. Обычные причины — опечатка в имени, забытая компиляция второго файла .c или использование математической функции без подключения математической библиотеки (-lm).

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ