Раздел справочника

Язык C

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

Обзор

Что важно понимать в C

C близок к машинной модели: данные имеют конкретное представление в памяти, указатели хранят адреса, а многие операции почти напрямую отображаются в машинный код. Именно поэтому язык широко используется в системном программировании, embedded-разработке, библиотеках, runtime-компонентах, сетевом коде и высокопроизводительных сервисах.

Ключевая идея: C не управляет ресурсами автоматически. Если программа выделила память, открыла файл, сокет или другой ресурс, она должна явно определить, кто владеет этим ресурсом и где он освобождается.

Явная память

Выделение и освобождение динамической памяти выполняется через malloc, calloc, realloc и free.

Простая модель функций

Функции принимают значения. Для изменения объекта вызывающей стороны обычно передают указатель на этот объект.

Undefined behavior

Выход за границы массива, use-after-free, разыменование невалидного указателя и signed overflow могут привести к неопределённому поведению.

Language

Конструкции языка

Types

Типы данных

Тип в C определяет размер объекта, допустимые операции, правила преобразования и интерпретацию битового представления. Для переносимого кода важно не полагаться на конкретный размер int, long или указателя без явной проверки.

Группа
Примеры
Комментарий
Целые
char, short, int, long, long long
Могут быть signed или unsigned. Размер зависит от платформы и ABI.
Фиксированной ширины
uint8_t, int32_t, uint64_t
Определены в <stdint.h>, удобны для протоколов и бинарных форматов.
Плавающая точка
float, double, long double
Используются для вещественных чисел, имеют ограничения точности.
Пользовательские
struct, union, enum, typedef
Позволяют описывать собственные структуры данных и имена типов.
#include <stdint.h>
#include <stddef.h>

struct packet_header {
    uint16_t type;
    uint16_t flags;
    uint32_t payload_size;
};

size_t header_size(void) {
    return sizeof(struct packet_header);
}

Memory

Память и указатели

Указатель хранит адрес объекта или функции. Через указатели C реализует передачу объектов по ссылке, работу с массивами, динамическую память, буферы, строки, callback-функции и интерфейсы системных API.

Automatic storage

Локальные переменные обычно живут до выхода из блока. Возвращать указатель на такую переменную нельзя.

Dynamic storage

Память, выделенная через malloc, живёт до вызова free или до завершения процесса.

Static storage

Глобальные и статические объекты существуют в течение всей работы программы.

Pointer arithmetic

Арифметика указателей выполняется в единицах размера типа, на который указывает указатель.

Практическое правило: каждый успешный вызов malloc, calloc или realloc должен иметь понятного владельца и соответствующий путь освобождения через free.
#include <stdlib.h>

int *make_array(size_t count) {
    int *data = malloc(count * sizeof(*data));
    if (data == NULL) {
        return NULL;
    }

    for (size_t i = 0; i < count; ++i) {
        data[i] = 0;
    }

    return data;
}

void destroy_array(int *data) {
    free(data);
}

Arrays

Массивы и строки

Массив в C — это непрерывная область элементов одного типа. В большинстве выражений имя массива преобразуется в указатель на первый элемент. Это часто называют array-to-pointer decay.

Важно: массив не хранит свой размер. Если функция принимает буфер, размер обычно передают отдельным аргументом.
#include <stddef.h>

int sum_array(const int *values, size_t count) {
    int sum = 0;

    for (size_t i = 0; i < count; ++i) {
        sum += values[i];
    }

    return sum;
}

Строки в C — это массивы символов, завершающиеся нулевым байтом '\0'. Большинство функций из <string.h> ожидают именно такую форму строки.

Data layout

Структуры, union и layout данных

struct объединяет несколько полей в один объект. Компилятор может добавлять padding между полями для соблюдения требований выравнивания. Поэтому бинарный layout структуры может зависеть от платформы, ABI и настроек компилятора.

#include <stdint.h>

struct point {
    int32_t x;
    int32_t y;
};

struct rectangle {
    struct point left_top;
    struct point right_bottom;
};

struct

Все поля существуют одновременно. Размер структуры не меньше суммы размеров её полей и может включать padding.

union

Несколько полей используют одну и ту же область памяти. В каждый момент корректно интерпретируется только активный вариант.

bit-fields

Позволяют описывать поля с заданным количеством бит, но их layout часто зависит от реализации.

Preprocessor

Препроцессор

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

#ifndef BUFFER_H
#define BUFFER_H

#include <stddef.h>

struct buffer {
    unsigned char *data;
    size_t size;
    size_t capacity;
};

#endif /* BUFFER_H */

Standard library

Стандартная библиотека C

Стандартная библиотека C предоставляет функции для работы с памятью, строками, вводом-выводом, математикой, временем, сортировкой, преобразованием чисел и базовой диагностикой ошибок.

Standards

Стандарты C

Стандарт
Ключевые возможности
Комментарий
C89/C90
Первый широко стандартизированный C, классическая база языка.
До сих пор встречается в старом коде и embedded-средах.
C99
stdint.h, inline, // comments, mixed declarations, designated initializers.
Важный практический стандарт для системного C.
C11
_Atomic, threads, static_assert, alignas/alignof через заголовки.
Добавляет модель памяти и средства многопоточности.
C17
Исправления и уточнения C11 без крупных новых возможностей.
Часто используется как стабильная база.
C23
Новые ключевые слова, улучшения constexpr-подобных выражений, атрибуты, уточнения библиотеки.
Постепенно появляется в новых компиляторах и проектах.

Examples

Небольшие примеры

Безопасное копирование буфера

#include <stddef.h>
#include <string.h>

int copy_bytes(
    unsigned char *dst,
    size_t dst_size,
    const unsigned char *src,
    size_t src_size
) {
    if (dst == NULL || src == NULL) {
        return -1;
    }

    if (src_size > dst_size) {
        return -1;
    }

    memcpy(dst, src, src_size);
    return 0;
}

Размер буфера передаётся явно. Функция не делает предположений о длине данных и проверяет границы перед копированием.

Передача структуры по указателю

#include <stddef.h>

struct stats {
    size_t packets;
    size_t errors;
};

void stats_add_packet(struct stats *s) {
    if (s == NULL) {
        return;
    }

    s->packets += 1;
}

void stats_add_error(struct stats *s) {
    if (s == NULL) {
        return;
    }

    s->errors += 1;
}

Если функция должна изменить объект вызывающей стороны, обычно передают указатель на этот объект.