Spec-Zone.ru › C

Объекты и выравнивание

Программы на C создают, уничтожают, получают доступ к объектам и манипулируют ими.

Объект в C — это область хранения данных в среде выполнения, содержимое которой может представлять значения (значение — это смысл содержимого объекта, интерпретируемого как имеющего определённый тип).

У каждого объекта есть

  • размер (можно определить с помощью sizeof)
  • требование к выравниванию(можно определить с помощью _Alignof(до C23)alignof(начиная с C23))(начиная с C11)
  • длительность хранения (автоматическая, статическая, выделенная, локальная для потока)
  • время жизни (равно длительности хранения или является временным)
  • эффективный тип (см. ниже)
  • значение (которое может быть неопределённым)
  • необязательно, идентификатор, обозначающий этот объект.

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

Представление объекта

За исключением битовых полей, объекты состоят из одной или нескольких непрерывных последовательностей байтов, каждая из которых содержит CHAR_BIT битов. Их можно скопировать с помощью memcpy в объект типа unsigned char[n], где n — размер объекта. Содержимое полученного массива называется представлением объекта.

Если два объекта имеют одинаковое представление, они сравниваются как равные (за исключением NaN с плавающей точкой). Обратное неверно: два равных объекта могут иметь разные представления, поскольку не каждый бит представления объекта обязательно должен участвовать в формировании значения. Такие биты могут использоваться для заполнения с целью соблюдения требований к выравниванию, проверки чётности, обозначения ловушечных представлений и т. д.

Если представление объекта не представляет ни одного значения типа этого объекта, оно называется ловушечным представлением. Доступ к ловушечному представлению любым способом, кроме чтения через выражение lvalue символьного типа, приводит к неопределённому поведению. Значение структуры или объединения никогда не является ловушечным представлением, даже если им является какой-либо отдельный член.

Для объектов типов char, signedchar и unsignedchar каждый бит представления объекта должен участвовать в представлении значения, а каждый возможный шаблон битов представляет отдельное значение (дополнительные биты, ловушечные биты и множественные представления не допускаются).

Когда объекты целочисленных типов (short, int, long, longlong) занимают несколько байтов, способ использования этих байтов определяется реализацией, однако наиболее распространены два варианта: от старшего байта к младшему (POWER, Sparc, Itanium) и от младшего байта к старшему (x86, x86_64). Платформа с порядком от старшего байта к младшему хранит старший байт по наименьшему адресу области памяти, занимаемой целым числом; платформа с обратным порядком хранит по наименьшему адресу младший байт. Подробнее см. в статье Порядок байтов. См. также пример ниже.

Хотя большинство реализаций не допускают ловушечных представлений, дополнительных битов или множественных представлений для целочисленных типов, существуют исключения; например, значение целочисленного типа на Itanium может быть ловушечным представлением.

Эффективный тип

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

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

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

  • При первой записи в этот объект через lvalue, имеющее тип, отличный от символьного, тип этого lvalue становится эффективным типом объекта для этой записи и всех последующих чтений.
  • memcpy или memmove копирует в этот объект другой объект либо копирует другой объект в этот объект как массив символьного типа; при этом эффективный тип исходного объекта (если он у него был) становится эффективным типом данного объекта для этой записи и всех последующих чтений.
  • При любом другом обращении к объекту без объявленного типа его эффективным типом становится тип lvalue, используемого для доступа.

Строгий алиасинг

Если объект имеет эффективный тип T1, использование выражения lvalue (обычно разыменование указателя) другого типа T2 приводит к неопределённому поведению, если только:

  • T2 и T1 не являются совместимыми типами.
  • T2 не является cvr-квалифицированной версией типа, совместимого с T1.
  • T2 не является знаковой или беззнаковой версией типа, совместимого с T1.
  • T2 не является агрегатным типом или типом объединения, содержащим среди своих членов один из упомянутых выше типов (в том числе рекурсивно — член подагрегата или вложенного объединения).
  • T2 не является символьным типом (char, signedchar или unsignedchar).
int i = 7;
char* pc = (char*)(&i);

if (pc[0] == '\x7') // aliasing through char is OK
    puts("This system is little-endian");
else
    puts("This system is big-endian");

float* pf = (float*)(&i);
float d = *pf; // UB: float lvalue *p cannot be used to access int

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

// int* and double* cannot alias
void f1(int* pi, double* pd, double d)
{
    // the read from *pi can be done only once, before the loop
    for (int i = 0; i < *pi; i++)
        *pd++ = d;
}
struct S { int a, b; };

// int* and struct S* may alias because S is an aggregate type with a member of type int
void f2(int* pi, struct S* ps, struct S s)
{
    // read from *pi must take place after every write through *ps
    for (int i = 0; i < *pi; i++)
        *ps++ = s;
}

Обратите внимание, что квалификатор restrict можно использовать, чтобы указать, что два указателя не ссылаются на одну и ту же область памяти, даже если приведённые выше правила это допускают.

Обратите внимание, что переинтерпретацию типа также можно выполнить через неактивный член объединения.

Выравнивание

У каждого полного типа объекта есть свойство, называемое требованием к выравниванию. Это целочисленное значение типа size_t, обозначающее количество байтов между последовательными адресами, по которым могут размещаться объекты этого типа. Допустимые значения выравнивания — неотрицательные целые степени двойки.

Требование к выравниванию типа можно запросить с помощью _Alignof(до C23)alignof(начиная с C23).

(начиная с C11)

Чтобы удовлетворить требованиям к выравниванию всех членов структуры, после некоторых её членов могут добавляться заполняющие байты.

#include <stdalign.h>
#include <stdio.h>

// objects of struct S can be allocated at any address
// because both S.a and S.b can be allocated at any address
struct S
{
    char a; // size: 1, alignment: 1
    char b; // size: 1, alignment: 1
}; // size: 2, alignment: 1

// objects of struct X must be allocated at 4-byte boundaries
// because X.n must be allocated at 4-byte boundaries
// because int's alignment requirement is (usually) 4
struct X
{
    int n;  // size: 4, alignment: 4
    char c; // size: 1, alignment: 1
    // three bytes padding
}; // size: 8, alignment: 4

int main(void)
{
    printf("sizeof(struct S) = %zu\n", sizeof(struct S));
    printf("alignof(struct S) = %zu\n", alignof(struct S));
    printf("sizeof(struct X) = %zu\n", sizeof(struct X));
    printf("alignof(struct X) = %zu\n", alignof(struct X));
}

Возможный вывод:

sizeof(struct S) = 2
alignof(struct S) = 1
sizeof(struct X) = 8
alignof(struct X) = 4

Каждый тип объекта накладывает своё требование к выравниванию на все объекты этого типа. Наименее строгое (наименьшее) выравнивание имеют типы char, signedchar и unsignedchar; оно равно 1. Наиболее строгое (наибольшее) фундаментальное выравнивание любого типа определяется реализациейи равно выравниванию max_align_t(начиная с C11).

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

Если выравнивание объекта сделано более строгим (большим), чем max_align_t, с помощью _Alignof(до C23)alignof(начиная с C23), оно имеет расширенное требование к выравниванию. Тип структуры или объединения, член которого имеет расширенное выравнивание, называется типом с повышенным выравниванием. Поддержка типов с повышенным выравниванием определяется реализацией; она может различаться для каждого вида длительности хранения.

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

Атомарная версия каждого арифметического или указательного типа имеет фундаментальное выравнивание.

(начиная с C11)

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены задним числом к ранее опубликованным стандартам C.

DR Применён к Поведение согласно опубликованной версии Исправленное поведение
DR 445 C11 тип мог иметь расширенное выравнивание без использования _Alignas он должен иметь фундаментальное выравнивание

Ссылки

  • Стандарт C17 (ISO/IEC 9899:2018):
    • 3.15 объект (стр. 5)
    • 6.2.6 Представления типов (стр. 33–35)
    • 6.2.8 Выравнивание объектов (стр. 36–37)
    • 6.5/6–7 Выражения (стр. 55–56)
  • Стандарт C11 (ISO/IEC 9899:2011):
    • 3.15 объект (стр. 6)
    • 6.2.6 Представления типов (стр. 44–46)
    • 6.2.8 Выравнивание объектов (стр. 48–49)
    • 6.5/6–7 Выражения (стр. 77)
  • Стандарт C99 (ISO/IEC 9899:1999):
    • 3.2 выравнивание (стр. 3)
    • 3.14 объект (стр. 5)
    • 6.2.6 Представления типов (стр. 37–39)
    • 6.5/6–7 Выражения (стр. 67–68)
  • Стандарт C89/C90 (ISO/IEC 9899:1990):
    • 1.6 Определения терминов

См. также

Документация по C++ для объектов

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/c/language/object

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API