Чтобы начать писать на языке C с нуля, хватит компилятора и текстового редактора. Сохраните программу в файл hello.c, соберите её командой gcc hello.c -o hello и запустите. Если ставить ничего не хочется, первые уроки можно пройти в онлайн-компиляторе прямо в браузере.
Дальше Си учат в одном и том же порядке: переменные и типы, ввод и вывод, условия и циклы, функции, массивы и строки, указатели. Ниже семь программ в этом порядке, под каждой её вывод. Какой компилятор поставить на свою систему, подробно разобрано в статье про компилятор C.
Что такое язык C и где на нём пишут

C (по-русски его называют Си) придумал Деннис Ритчи из Bell Labs в начале 1970-х как язык для операционной системы Unix, которую тогда только начинали писать. По воспоминаниям самого Ритчи, язык складывался в 1969–1973 годах, а самый плодотворный период пришёлся на 1972-й.
Язык компилируемый: программу сначала переводят в машинный код, и только потом запускают. Памятью программист управляет сам, сборщика мусора нет. Отсюда и скорость, и главные ошибки новичков, о них ниже.
Язык живёт и обновляется. Актуальный стандарт называется C23, его приняли ISO и IEC в 2024 году.
Где C встречается чаще всего:
| Область | Пример | Что подтверждает |
|---|---|---|
| Операционные системы | Ядро Linux | Документация ядра: написано на C, обычно собирается GCC в режиме -std=gnu11 |
| Базы данных | SQLite | Страница проекта: со дня создания 29 мая 2000 года SQLite пишут на C |
| Микроконтроллеры и встраиваемые системы | Прошивки бытовой техники, датчиков, контроллеров | Подробно в статье о программисте микроконтроллеров |
Чем на C зарабатывают и как выглядит рабочий день, мы рассказали в разборе профессии разработчика на Си. Если тянет к железу, посмотрите на программирование Arduino: скетчи пишут на C++, но базовый синтаксис у него с C общий.
Что установить, чтобы писать на Си
Нужны две вещи: редактор, где вы пишете код, и компилятор, который превращает код в программу. Редактор подойдёт любой, кроме Word: он добавляет в файл форматирование, которое компилятор не поймёт.
| Система | Компилятор | Как поставить |
|---|---|---|
| Windows | MSVC или GCC | MSVC приходит вместе с Visual Studio: в установщике выберите рабочую нагрузку «Разработка классических приложений на C++», язык C в неё тоже входит. GCC ставят через MSYS2 |
| macOS | Clang | Команда xcode-select --install в Терминале |
| Ubuntu и Debian | GCC | Команда sudo apt install build-essential, пакет тянет за собой gcc, make и заголовочные файлы |
В Windows компилятор Microsoft запускают из «Командной строки разработчика» командой cl hello.c, так показано в пошаговом руководстве Microsoft. Пошаговая установка GCC через MSYS2 с командами есть в нашей статье про компиляторы, ссылка в начале.
Без установки код на C запускают в браузере. В OnlineGDB можно ввести данные с клавиатуры, это пригодится для примеров со scanf. Compiler Explorer показывает, во что компилятор превращает каждую строку, и умеет переключаться между версиями GCC и Clang.
Первая программа на C
Создайте файл hello.c и вставьте в него шесть строк:
#include <stdio.h>
int main(void) {
printf("Привет, мир!\n");
return 0;
}
Соберите и запустите:
gcc -std=c17 -Wall -Wextra hello.c -o hello
./hello
Привет, мир!
Что здесь происходит, построчно:
#include <stdio.h>подключает стандартную библиотеку ввода-вывода, где живётprintf.int main(void)объявляет главную функцию. С неё начинается любая программа на C.printfпечатает строку, а\nпереводит курсор на новую строку.return 0сообщает системе, что программа завершилась без ошибок.
Флаги в команде сборки стоит запомнить сразу. -std=c17 фиксирует версию языка, а -Wall -Wextra включают предупреждения. Без флага -std компиляторы выбирают версию сами: Clang работает в режиме gnu17, а GCC начиная с 15-й версии перешёл на gnu23. Код из этой статьи собирается в обоих режимах.
В Windows GCC создаст файл hello.exe, в командной строке его запускают командой hello.exe.
Что происходит при сборке программы

Команда gcc hello.c -o hello выглядит как один шаг, но внутри их четыре. В документации GCC они названы так: препроцессор, компиляция, ассемблирование и компоновка. Каждый этап можно остановить флагом и посмотреть на промежуточный файл:
| Этап | Команда | Что получится |
|---|---|---|
| Препроцессор | gcc -E hello.c -o hello.i |
Текст программы, в который вставлено содержимое stdio.h. В Ubuntu 24.04 файл из шести строк вырос до 819 |
| Компиляция | gcc -S hello.c -o hello.s |
Код на языке ассемблера |
| Ассемблирование | gcc -c hello.s -o hello.o |
Объектный файл с машинным кодом |
| Компоновка | gcc hello.o -o hello |
Готовая программа, к которой подключена стандартная библиотека |
Зачем это новичку: сообщение об ошибке подсказывает, на каком этапе сломалось. No such file or directory про заголовочный файл приходит от препроцессора, синтаксическая ошибка от компилятора, а undefined reference to от компоновщика, когда функция объявлена, но её тело нигде не найдено. Разбор таких сообщений с примерами есть в статье о компиляторах C, в разделе о частых ошибках.
Переменные и типы данных
В C у каждой переменной есть тип, и задают его до первого использования. Тип определяет, сколько памяти займёт значение и какие операции с ним можно делать.
| Тип | Что хранит | Пример | Формат в printf |
|---|---|---|---|
int |
Целое число | int apples = 7; |
%d |
double |
Дробное число | double price = 89.90; |
%f, %.2f |
char |
Один символ | char grade = 'A'; |
%c |
char[] |
Строка | char name[] = "Си"; |
%s |
Размер типов стандарт не фиксирует жёстко. Для int он гарантирует не меньше 16 бит, а sizeof(char) всегда равен 1. Как числа раскладываются на биты, удобно посмотреть в разборе систем счисления: примеры там на Python, но двоичная запись одна для всех языков. На обычном ноутбуке int занимает 4 байта, это видно в выводе программы:
#include <stdio.h>
int main(void) {
int apples = 7;
int people = 2;
double price = 89.90;
char grade = 'A';
printf("Каждому по %d, остаток %d\n", apples / people, apples % people);
printf("Точное деление: %.1f\n", (double)apples / people);
printf("Цена: %.2f, оценка: %c\n", price, grade);
printf("int занимает %zu байта, double — %zu\n", sizeof(int), sizeof(double));
return 0;
}
Каждому по 3, остаток 1
Точное деление: 3.5
Цена: 89.90, оценка: A
int занимает 4 байта, double — 8
Деление двух целых даёт целое. По стандарту начиная с C99 дробная часть отбрасывается в сторону нуля, поэтому 7 / 2 равно 3. Чтобы получить 3.5, одно из чисел приводят к double, как во второй строке примера. Остаток от деления считает оператор %.
Ввод и вывод: printf и scanf
Печатает данные printf, читает с клавиатуры scanf. Формат у них общий: %d для целого, %lf для double при чтении, %s для строки.
#include <stdio.h>
int main(void) {
int year;
printf("Год рождения: ");
if (scanf("%d", &year) != 1) {
printf("Нужно целое число\n");
return 1;
}
printf("В 2030 году вам исполнится %d\n", 2030 - year);
return 0;
}
Год рождения: 1995
В 2030 году вам исполнится 35
Две детали, в которых новички ошибаются чаще всего:
- Знак
&перед переменной. Функцияscanfдолжна записать число вyear, поэтому получает её адрес, а не значение. Откуда берётся адрес, станет ясно в разделе про указатели. - Проверка результата.
scanfвозвращает число успешно заполненных переменных: на «abc» вместо числа это будет 0, а если ввод закончился раньше, чем удалось заполнить первую, вернётсяEOF. Введёте «abc» вместо года, и программа из примера ответит «Нужно целое число», а не посчитает возраст от мусора в памяти.
Условия и циклы
Ветвление в C пишут через if и else, повторение через for и while. Сравнение записывают двумя знаками ==, один знак = означает присваивание. Эту пару легко перепутать, особенно после Паскаля или Python.
#include <stdio.h>
int main(void) {
int n = 10;
int sum = 0;
for (int i = 1; i <= n; i++) {
if (i % 2 == 0) {
sum += i;
}
}
printf("Сумма чётных от 1 до %d: %d\n", n, sum);
int x = 1000;
int steps = 0;
while (x > 1) {
x = x / 2;
steps++;
}
printf("1000 делили пополам %d раз\n", steps);
return 0;
}
Сумма чётных от 1 до 10: 30
1000 делили пополам 9 раз
Цикл for берут, когда число повторений известно заранее. while подходит, когда цикл крутится, пока выполняется условие: здесь мы не знаем наперёд, сколько раз 1000 поделится пополам до единицы.
Если вы уже писали на школьном Паскале, структура покажется знакомой. Разница в деталях, и её удобно сравнить с нашим разбором Паскаля для начинающих.
Функции
Функция в C объявляет тип результата, имя и параметры. Если функция описана ниже main, перед main ставят её прототип, иначе компилятор не будет знать о ней в момент вызова.
#include <stdio.h>
int is_prime(int n);
int main(void) {
for (int i = 2; i <= 30; i++) {
if (is_prime(i)) {
printf("%d ", i);
}
}
printf("\n");
return 0;
}
int is_prime(int n) {
if (n < 2) {
return 0;
}
for (int d = 2; d * d <= n; d++) {
if (n % d == 0) {
return 0;
}
}
return 1;
}
2 3 5 7 11 13 17 19 23 29
Отдельного логического типа в старых стандартах не было, поэтому функция возвращает 1 для «да» и 0 для «нет». Любое ненулевое число if считает истиной. Делители проверяем только до квадратного корня из числа: если у n есть делитель больше корня, парный к нему окажется меньше корня и найдётся раньше.
Массивы и строки
Массив хранит несколько значений одного типа подряд. Нумерация начинается с нуля, так что у массива из семи элементов последний индекс 6. Размер задают при объявлении, и сам массив не растёт: в C нет аналога метода push из JavaScript, для этого вручную выделяют новую память.
Строка в C устроена как массив символов, в конце которого стоит невидимый нулевой символ \0. Из-за него sizeof у строки «hello» на единицу больше, чем её длина по strlen:
#include <stdio.h>
#include <string.h>
int main(void) {
double temps[7] = {12.5, 14.0, 9.5, 11.0, 15.5, 13.0, 10.5};
double total = 0;
for (int i = 0; i < 7; i++) {
total += temps[i];
}
printf("Средняя за неделю: %.1f\n", total / 7);
char word[] = "hello";
printf("Длина: %zu, символов в массиве: %zu\n", strlen(word), sizeof(word));
for (int i = (int)strlen(word) - 1; i >= 0; i--) {
printf("%c", word[i]);
}
printf("\n");
return 0;
}
Средняя за неделю: 12.3
Длина: 5, символов в массиве: 6
olleh
Границы массива C не проверяет. Обращение к temps[7] скомпилируется и, скорее всего, запустится, просто прочитает чужую память. Как ловить такие ошибки, показано ниже.
Указатели: первое знакомство
Указатель хранит адрес другой переменной. Оператор & берёт адрес, оператор * достаёт значение по адресу. Без указателей функция не может изменить переменные того, кто её вызвал: в C аргументы передаются копиями.
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 3;
int y = 8;
int *p = &x;
printf("x = %d, через указатель: %d\n", x, *p);
swap(&x, &y);
printf("После обмена: x = %d, y = %d\n", x, y);
return 0;
}
x = 3, через указатель: 3
После обмена: x = 8, y = 3
В C++ ту же задачу решают ещё и ссылками, но указатели там остаются, поэтому эта тема пригодится, если вы потом пойдёте в C++.
Теперь понятно, зачем scanf нужен &year: функция получает адрес и пишет число прямо в вашу переменную. После основ идут динамическая память через malloc и free и структуры, и на этом этапе указатели встречаются уже в каждой программе.
Частые ошибки новичков
Ниже семь типичных ошибок первых программ. Компиляторы реагируют на них по-разному, поэтому для каждой указано, что говорят GCC 13 и Clang 17 с флагами -std=c17 -Wall -Wextra.
Присваивание вместо сравнения
Строка if (score = 10) записывает 10 в переменную, и условие всегда истинно. Оба компилятора предупреждают. GCC пишет:
warning: suggest parentheses around assignment used as truth value [-Wparentheses]
Решение: в сравнении ставить ==.
Забытый & в scanf
Вызов scanf("%d", age) передаёт значение вместо адреса, и программа пишет число неизвестно куда. Оба компилятора ловят это предупреждением -Wformat: ждали int *, получили int.
Точка с запятой после if
В записи if (n > 5); условие ничего не охраняет: точка с запятой закончила оператор, а блок в фигурных скобках выполнится всегда. Clang предупреждает if statement has empty body, GCC советует поставить фигурные скобки.
Переменная без начального значения
В int sum; лежит то, что было в памяти до вас, и sum += i прибавляет к мусору. Clang ловит это при обычной сборке. GCC 13 в нашем примере промолчал без оптимизации и выдал предупреждение только с флагом -O2.
Решение: инициализируйте переменные сразу, int sum = 0;.
Выход за границы массива
Цикл for (int i = 0; i <= 5; i++) по массиву из пяти элементов читает шестой, которого нет. Clang 17 на этом коде молчит, GCC 13 предупреждает только с -O2. Такие ошибки ловит AddressSanitizer: соберите программу с флагом -fsanitize=address, и при запуске она остановится с отчётом.
ERROR: AddressSanitizer: stack-buffer-overflow
READ of size 4 ...
#0 ... in main err_bounds.c:6
Целочисленное деление
Строка double half = 1 / 2; даёт 0, потому что делятся два целых. Ни один из двух компиляторов здесь не предупредил: с точки зрения языка код правильный. Пишите 1.0 / 2 или приводите тип явно.
Неверный формат в printf
Если вывести double через %d, на экране появится случайное число. Оба компилятора предупреждают через -Wformat, поэтому флаг -Wall лучше не выключать никогда.
Все семь случаев объединяет одно: код собирается, а работает неправильно. Поэтому предупреждения компилятора стоит читать так же внимательно, как ошибки.
Задачи для практики

Четыре задачи на темы статьи. Каждая решается программой до 30 строк.
- Таблица температур. Выведите пересчёт из градусов Цельсия в Фаренгейты от 0 до 100 с шагом 10. Формула: F = C × 9 / 5 + 32. Проверьте себя: 100 °C дают 212 °F. Если вышло 132, вы записали
9 / 5 * c: целые 9 / 5 дают 1, и формула ломается. - Сумма цифр. Прочитайте целое число через
scanfи посчитайте сумму его цифр цикломwhile: остаток% 10даёт последнюю цифру, деление/ 10её отрезает. Для 2026 ответ 10. - Максимум в массиве. Заполните массив из десяти чисел и найдите наибольшее и его индекс. Не забудьте, что индексы идут от 0 до 9.
- Разворот строки на месте. Напишите функцию
void reverse(char *s), которая меняет символы местами с двух концов, пока индексы не встретятся. Длину возьмите черезstrlen.
Как учить C дальше
Порядок тем после основ: указатели и динамическая память, структуры, работа с файлами, сборка проекта из нескольких файлов через make. Параллельно стоит завести репозиторий для упражнений, основы Git простыми словами займут вечер.
- Книга Кернигана и Ритчи «Язык программирования C». Создатель языка написал её вместе с Брайаном Керниганом. Книга тонкая и плотная, упражнения в ней хорошие, но новичку без опыта программирования она даётся тяжело.
- Справочник cppreference по стандартной библиотеке C. Там описано, что возвращает каждая функция и в какой версии стандарта она появилась.
- Курс CS50 Гарвардского университета. После вводной недели на Scratch программа курса переходит на C, а неделя 4 называется Memory и разбирает работу с памятью. Материалы бесплатные, на английском.
- Задачи на каждую тему. Пишите по пять-десять маленьких программ на тему, прежде чем идти дальше. Как выстроить самостоятельную учёбу, мы разбирали в статье о том, как учить языки программирования самостоятельно.
Первым языком C выбирать можно, но готовьтесь, что ошибки с памятью съедят часть мотивации. Если цель просто научиться программировать, начинать с Python проще, сравнение подходов есть в материале о программировании с нуля. Если вас интересуют встраиваемые системы, драйверы или устройство компьютера изнутри, C ведёт туда напрямую. Путь до первой работы на Си расписан в плане как стать программистом на Си.
C и C++ часто учат парой. C++ вырос из C и добавил классы, шаблоны и большую стандартную библиотеку. Синтаксис основ у них общий, поэтому база из этой статьи пригодится и там.
Где освоить язык Си системно
Основы из статьи можно пройти самостоятельно. Дальше начинаются указатели, память и сборка проектов, и тут удобнее учиться по программе с проверкой заданий. Ниже курсы по языку C из нашего каталога, а если хотите сразу двигаться к C++, посмотрите курсы по C++.
| Курс | Школа | Стоимость со скидкой | В рассрочку | Длительность | Обзор курса от Checkroi |
|---|---|---|---|---|---|
| Инженер-программист Перейти на сайт курса | 159 408 ₽ | 4428 ₽/мес. | 12 месяцев | Обзор курса | |
| Специалист по информационной безопасности: расширенный курс Перейти на сайт курса | 117 343 ₽ | 4018 ₽/мес. | 13 месяцев | Обзор курса | |
| Внедрение и работа в DevSecOps Перейти на сайт курса | 88 000 ₽ | 8800 ₽/мес. | 5 месяцев | Обзор курса | |
| Блокчейн Перейти на сайт курса | 168 768 ₽ | 4153 ₽/мес. | 4 месяца | Обзор курса | |
| Инженер-программист C++ Перейти на сайт курса | 7970 ₽ | 221 ₽/мес. | 1 месяц | Обзор курса |
Больше программ — в полном каталоге курсов по C (язык Си)




