Гарантированное расположение памяти для стандартной структуры размещения с одним элементом массива примитивного типа - PullRequest
20 голосов
/ 12 апреля 2019

Рассмотрим следующую простую структуру:

struct A
{
    float data[16];
};

Мой вопрос:

Предполагается, что платформа, где float - это 32-битное число с плавающей точкой IEEE754 (если это вообще имеет значение), гарантирует ли стандарт C ++ ожидаемое расположение памяти для struct A?Если нет, то что это гарантирует и / или каковы способы обеспечения гарантий ?

Под ожидаемым расположением памяти я имею в видучто структура занимает 16*4=64 байтов в памяти, каждый последовательный 4 байтов занят одним float из массива data.Другими словами, ожидаемая схема памяти означает следующие тестовые прохождения:

static_assert(sizeof(A) == 16 * sizeof(float));
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));

(offsetof здесь допустимо, поскольку A - стандартная схема, см. Ниже)

В случае, если это вас беспокоит, тест фактически проходит на wandbox с gcc 9 HEAD.Я никогда не встречал комбинацию платформы и компилятора, которая бы свидетельствовала о том, что этот тест может провалиться, и я хотел бы узнать о них, если они существуют.

Почему кого-то это волнует:

  • SSE-подобные оптимизации требуют определенной схемы памяти (и выравнивания, которое я игнорирую в этом вопросе, поскольку с ним можно справиться, используя стандартный спецификатор alignas).
  • Сериализация такогоstruct просто сводится к хорошему и переносимому write_bytes(&x, sizeof(A)).
  • Некоторые API (например, OpenGL, в частности, скажем, glUniformMatrix4fv ) ожидают такой точной компоновки памяти.Конечно, можно просто передать указатель на массив data, чтобы передать один объект этого типа, но для последовательности из них (скажем, для загрузки атрибутов вершин матричного типа) определенная структура памяти все еще необходима.

Что на самом деле гарантировано:

Это то, что, насколько мне известно, можно ожидать от struct A:

  • Это стандартный макет
  • Как следствие стандартного макета, указатель на A может быть reinterpret_cast на указатель на его первый элемент данных (который, предположительно, data[0]?)т. е. там нет заполнения до первого члена.

Две оставшиеся гарантии, которые не (насколько мне известно), предоставляемые стандартом:

  • Нет заполнения между элементами массива примитивного типа (яуверен, что это неверно, но мне не удалось найти подтверждающую ссылку),
  • Заполнение отсутствует после массив data внутри struct A.

Ответы [ 2 ]

12 голосов
/ 12 апреля 2019

Одна вещь, которая не гарантируется в компоновке, это порядковый номер, то есть порядок байтов в многобайтовом объекте.write_bytes(&x, sizeof(A)) не является переносимой сериализацией в системах с различным порядком байтов.

A может быть reinterpret_cast указателем на его первый элемент данных (который, предположительно, data[0]?)

Исправление: Первый элемент данных - data, который можно интерпретировать с помощью приведения.И что очень важно, массив не является взаимозаменяемым по указателю с его первым элементом, поэтому вы не можете интерпретировать приведение между ними.Однако адрес гарантированно будет таким же, поэтому, как я понимаю, после std::launder все будет в порядке, так как data[0] будет в порядке.

Между элементами массива примитива нет заполнения.тип

Массивы гарантированно будут смежными.sizeof объекта указывается в терминах заполнения, необходимого для размещения элементов в массиве.sizeof(T[10]) имеет в точности размер sizeof(T * 10).Если между не дополняющими битами смежных элементов есть заполнение, то это заполнение находится в конце самого элемента.

Примитивный тип не гарантирует, что заполнение вообще не будет.Например, расширенная точность x86 long double равна 80 битам, дополненная 128 битами.

char, signed char и unsigned char гарантированно не будут иметь битов заполнения.Стандарт C (которому C ++ делегирует спецификацию в данном случае) гарантирует, что псевдонимы фиксированной ширины intN_t и uintN_t не имеют битов заполнения.В системах, где это невозможно, эти типы фиксированной ширины не предоставляются.

2 голосов
/ 12 апреля 2019

Если объект класса стандартной компоновки имеет какие-либо нестатические члены-данные, его адрес совпадает с адресом его первого нестатического члена данных. В противном случае его адрес совпадает с адресом его первой базы. подобъект класса (если есть). [Примечание: Следовательно, в объекте структуры стандартной компоновки может быть безымянный отступ, но не в его начале, что необходимо для достижения соответствующего выравнивания. - конец примечания]

Следовательно, стандарт гарантирует, что

static_assert(offsetof(A, data[0]) == 0 * sizeof(float));

Объект типа массива содержит непрерывно размещенный непустой набор из N подобъектов типа T.

Следовательно, верно следующее

static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));
...