Namespaces
Variants

Objects and alignment

From cppreference.net

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

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

Каждый объект имеет

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

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

Содержание

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

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

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

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

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

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

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

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

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

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

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

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

Строгое псевдонимирование

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

  • T2 и T1 являются совместимыми типами .
  • T2 является cvr-квалифицированной версией типа, который совместим с T1.
  • T2 является знаковой или беззнаковой версией типа, который совместим с T1.
  • T2 является агрегатным типом или типом объединения, который включает один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член под-агрегата или содержащегося объединения).
  • T2 является символьным типом ( char , signed char , или unsigned char ).
int i = 7;
char* pc = (char*)(&i);
if (pc[0] == '\x7') // алиасинг через char допустим
    puts("Эта система с little-endian порядком байт");
else
    puts("Эта система с big-endian порядком байт");
float* pf = (float*)(&i);
float d = *pf; // UB: float lvalue *p не может использоваться для доступа к int

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

// int* и double* не могут быть алиасами
void f1(int* pi, double* pd, double d)
{
    // чтение из *pi может быть выполнено только один раз, до цикла
    for (int i = 0; i < *pi; i++)
        *pd++ = d;
}
struct S { int a, b; };
// int* и struct S* могут алиасить, так как S является агрегатным типом с членом типа int
void f2(int* pi, struct S* ps, struct S s)
{
    // чтение из *pi должно происходить после каждой записи через *ps
    for (int i = 0; i < *pi; i++)
        *ps++ = s;
}

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

Обратите внимание, что type-punning также может быть выполнен через неактивный член union .

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

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

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

(начиная с C11)

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

#include <stdalign.h>
#include <stdio.h>
// объекты структуры S могут быть размещены по любому адресу
// так как и S.a, и S.b могут быть размещены по любому адресу
struct S
{
    char a; // размер: 1, выравнивание: 1
    char b; // размер: 1, выравнивание: 1
}; // размер: 2, выравнивание: 1
// объекты структуры X должны быть размещены по 4-байтным границам
// потому что X.n должен быть размещен по 4-байтным границам
// так как требование выравнивания для int (обычно) равно 4
struct X
{
    int n;  // размер: 4, выравнивание: 4
    char c; // размер: 1, выравнивание: 1
    // три байта заполнения
}; // размер: 8, выравнивание: 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 , signed char и unsigned char , и равно 1 . Наибольшее (самое строгое) фундаментальное выравнивание любого типа определяется реализацией и равно выравниванию max_align_t (since C11) .

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

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

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

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

(начиная с C11)

Отчеты о дефектах

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

DR Applied to Behavior as published Correct behavior
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 object (стр. 6)
  • 6.2.6 Representations of types (стр. 44-46)
  • 6.2.8 Alignment of objects (стр. 48-49)
  • 6.5/6-7 Expressions (стр. 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++ documentation для Object