§ 9. Структуры, битовые поля и объединения

§ 9.1 Структуры

Структура — это общее имя для набора переменных. Если переменная — это тарелка, то структура — это поднос с тарелками.

struct Point
{
    int x;
    int y;
};

Переменные одного типа можно объявлять вместе. Давайте создадим один экземпляр (назовём его point), а затем заполним поля x и y (используя оператор точка .).

struct Point
{
    int x, y;
};

int main()
{
    struct Point point;
    point.x = 0;
    point.y = 0;
}

Как и массивы, структуры можно инициализировать при объявлении.

struct Point point = {0, 0};

А начиная со стандарта C99, можно инициализировать с указанием полей (чего нет в C++).

struct Point point = {.x = 0, .y = 0};

Структуры могут быть вложенными.

struct Date
{
    int day;
    int month;
    int year;
};

struct Person
{
    char name[64];
    struct Date birthday;
};

Можно записать иначе.

struct Person
{
    char name[64];
    struct Date // Имя можно не писать.
    {
        int day;
        int month;
        int year;
    } birthday;
};

Обращение к полям структуры в структуре будет длиннее, но всё ещё более-менее понятным.

int main()
{
    struct Person person;
    strcpy(person.name, "Neo");
    person.birthday.day = 0;
    person.birthday.month = 0;
    person.birthday.year = 0;
}

Структура также может быть объявлена внутри функции, поэкспериментируйте самостоятельно.

Структура может быть сперва объявлена, а лишь затем определена.

struct Point;
...
struct Point
{
    int x;
    int y;
};

§ 9.2 Способы создания экземпляров

struct Person person;
struct Person
{
    ...
} person;
struct Person* person = malloc(sizeof(struct Person));
struct Person
{
    ...
} *person;

В последнем случае также нужно вручную выделять память.

Если мы работаем с экземпляром структуры по указателю, то оператор точка . заменяется на ->.

Почему постоянно приходится писать слово struct? В языке C (но не в языке C++) структуры находятся в отдельном пространстве имён (которые не имеют ничего общего с пространствами имён языка C++). Чтобы постоянно не писать struct, можно единожды определить псевдоним одним из способов.

typedef struct Person
{
    ...
};
typedef struct Person Person;

Структура может быть анонимной, то есть не иметь имени. Тогда создавать экземпляры такой структуры можно только при определении смой структуры.

struct {
    ...
} person;

Можно создавать массивы структур, структуры могут содержать массивы и так далее.

§ 9.3 Передача структуры в функцию

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

void foo(struct Point point)
{
    point.x = 0;
    point.y = 0;
}

При передаче по указателю (если он не константный) будут изменяться поля самой переданной структуры, а не локальной копии.

void foo(struct Point* point)
{
    point->x = 0;
    point->y = 0;
}

§ 9.4 Выравнивание структур

32-битная машина быстрее всего работает с адресами, кратными 4. Взгляните на такую структуру.

struct Bar
{
    char a, b;    // aaaaaaaa bbbbbbbb -------- --------
    int c;        // cccccccc cccccccc cccccccc cccccccc
};

Третье поле, занимающее 4 байта, размещено отдельно, а перед ним добавлено пустое место. Это называется выравниванием. Оно предназначено для ускорения операций доступа. Поэтому не стоит удивляться, что суммарный размер структуры — 8 байтов.

printf("%zu", sizeof(struct Bar)); // 8

Можно воспользоваться средствами компилятора для тонкой настройки выравнивания.

§ 9.5 Битовые поля

Несколько логических значений можно упаковать в одном байте (например, для передачи на интерфейсы некоторых устройств). Битовое поле — это особый вид структуры.

struct Params
{
    unsigned param1: 1; // Длина поля 1.
    unsigned param2: 2;
    unsigned param3: 1;
};

Битовые поля должны иметь тип int или unsigned. Зато можно смешивать битовые поля и любые типы. Битовые поля длиной 1 должны иметь тип unsigned (из-за бита знака). Нельзя получить адрес переменной битового поля. Переменные битового поля не могут размещаться в массиве. И снова встаёт проблема порядка битов.

§ 9.6 Объединения

Объединения позволяют хранить переменные разных типов в одном и том же месте памяти. Объединения объявляются как структуры, но пусть вас это не обольщает.

union IntOrChar
{
    int i;
    char ch;
};

Экземпляры объединений создаются так же, как у структур.

Если int в памяти занимает 4 байта, а char — 1 байт, то всё объединение будет иметь размер 4 байта.

В следующем примере создаётся объединение из типов int и float. Значение с плавающей запятой записывается в объединение, а считывается как целое. Приведения типа не происходит, зато происходит интерпретация битов так, будто сперва это биты числа с плавающей запятой, а потом — биты целого числа.

typedef union IntOrFloat
{
    int i;
    float f;
};
IntOrFloat intOrFloat;
intOrFloat.f = 3.1415f;
printf("%d", intOrFloat.i); // 1078529622
14 июня