Структуры: объединяем данные
Урок вводит структуры — способ объединить разнородные данные в один тип: как объявить struct, обращаться к полям и как структура раскладывается в памяти с учётом выравнивания.
Структура — это пользовательский тип, склеивающий несколько полей в одно целое. В памяти поля лежат подряд, но компилятор может вставлять между ними пустые байты (padding) ради выравнивания.
Если массив хранит много значений одного типа, то структура объединяет разнородные данные, описывающие один объект. Объявим тип «точка» и тип «студент»:
struct Point {
int x;
int y;
};
struct Student {
char name[32];
int age;
double gpa;
};
int main(void) {
struct Point p = {3, 4};
p.x = 10; // доступ к полю через точку
printf("(%d, %d)\n", p.x, p.y);
struct Student s = {"Anna", 20, 4.8};
printf("%s, %d лет, GPA %.1f\n", s.name, s.age, s.gpa);
return 0;
}
К полям обращаются через точку: p.x, s.age. Структуру можно инициализировать списком значений в порядке полей. Это основа для построения сложных типов данных.
Как работает под капотом
Поля структуры лежат в памяти подряд, в порядке объявления. Но процессор эффективнее читает данные, выровненные по своему размеру, поэтому компилятор иногда вставляет «дыры» — padding:
struct Example {
char c; // 1 байт
int n; // 4 байта
};
Наивно ожидаем 5 байт, но в памяти:
смещение: 0 1 2 3 4 5 6 7
+----+---+---+---+ +----+----+----+----+
| c |PADDING (3)| | n (4 байта) |
+----+---+---+---+ +----+----+----+----+
^ ^^^^^^^^^^^ ^
| вставлено n выровнен по адресу,
char ради кратному 4
выравнивания
sizeof(struct Example) = 8, а не 5!
Поэтому sizeof структуры может быть больше суммы размеров полей. Порядок полей влияет на размер: если сгруппировать поля от больших к меньшим, padding часто уменьшается. Это важно, когда структур миллионы или они идут в файл/сеть.
Частые ошибки
- Ожидание, что sizeof = сумме полей. Из-за padding размер обычно больше; нельзя полагаться на «ручной» подсчёт.
- Сравнение структур через
==. Структуры нельзя сравнивать оператором==; сравнивают поле за полем. - Передача большой структуры по значению. Структура копируется целиком — для больших структур это дорого; передавайте указатель.
- Забыли
structв имени типа. В C (без typedef) тип называетсяstruct Point, а не простоPoint.
Best practices
- Используйте
typedef, чтобы не писатьstructкаждый раз:typedef struct { int x, y; } Point;. - Большие структуры передавайте в функции по указателю (и
const-указателю, если только читаете), чтобы избежать дорогого копирования. - Группируйте поля от больших к меньшим, если размер структуры критичен.
Идею «склеить разнородные поля в один объект» в Python естественно показать через словарь. Заодно увидим, что Python не заботится о padding — это особенность низкого уровня C.
# Структура C как словарь полей
student = {
"name": "Anna",
"age": 20,
"gpa": 4.8,
}
# доступ к полю — аналог s.age в C
print(student["name"], student["age"], "лет, GPA", student["gpa"])
# "массив структур" — список таких объектов
group = [
{"name": "Anna", "age": 20},
{"name": "Ivan", "age": 22},
]
for s in group:
print(f"{s['name']}: {s['age']}")
Та же логика на Python ▶ — словарь группирует разнородные поля, как struct в C. Разница: в C это типизированный блок памяти с предсказуемой раскладкой и padding, а в Python — гибкий объект.
Структуры как основа всего
Структуры — это не просто «удобная группировка»; на них держится вся архитектура крупных программ на C. Не имея классов, C выражает объекты предметной области именно структурами: соединение с базой, окно интерфейса, частица в физическом движке, узел дерева — всё это структуры с полями. А функции, принимающие указатель на структуру первым аргументом, играют роль «методов»: account_deposit(&acc, 100) — это, по сути, acc.deposit(100) из объектных языков. Так в C вручную собирают то, что в C++ или Java встроено в язык. Понимание, что объектно-ориентированный код можно выразить через «структура плюс функции над ней», демистифицирует ООП и показывает, что под капотом любого языка лежат всё те же данные в памяти и функции, которые их обрабатывают.
Итоги
Структура объединяет разнородные поля в один пользовательский тип; доступ к полям — через точку. В памяти поля лежат подряд, но компилятор вставляет padding ради выравнивания, поэтому sizeof структуры может превышать сумму размеров полей и зависит от порядка полей. Большие структуры передавайте по указателю, а typedef избавит от повторения слова struct.