Гарантируется ли выравнивание элементов POD-структуры или стандартного типа макета в соответствии с их требованиями к выравниванию?
При наличии POD-структуры (в C++03) или стандартного типа макета (в C++11) со всеми элементами, имеющими фундаментальное требование выравнивания, верно, что каждый элемент гарантированно выровнен в соответствии со своим требованием выравнивания?
Другими словами, для всех участников m_k
в { m0
... mn
} стандартного типа макета S
,
struct S {
T0 m0;
T1 m1;
...
TN mn;
};
является ли следующее выражение гарантированным для true
?
(offsetof(S,m_k) % alignof(decltype(S::m_k))) == 0
Пожалуйста, дайте ответы как для C++ 03, так и для C++ 11 и приведите соответствующие части стандарта. Вспомогательные доказательства из стандартов C также будут полезны.
Мое чтение стандарта C++ 03 (ISO/IEC 14882:2003(E)) заключается в том, что в нем ничего не говорится о выравнивании элементов в POD-структуре, за исключением первого члена. Соответствующие пункты:
На языке спецификации объект является "областью хранения":
1.8 Объектная модель C++ + [intro.object]
1.8 / 1 Конструкции в программе C++ создают, уничтожают, обращаются к объектам, обращаются к ним и управляют ими. Объект - это область хранения....
Объекты распределяются в соответствии с требованием их выравнивания:
3.9 Типы [базовые.типы]
3.9/5 Типы объектов имеют требования к выравниванию (3.9.1, 3.9.2). Выравнивание полного типа объекта представляет собой целочисленное значение, определяемое реализацией, представляющее количество байтов; объект размещается по адресу, который соответствует требованиям выравнивания его типа объекта.
Основные типы имеют требования к выравниванию:
3.9.1 Фундаментальные типы [basic.fundamental]
3.9.1/3 Для каждого из целочисленных типов со знаком существует соответствующий (но отличающийся) целочисленный тип без знака: "unsigned char", "unsigned short int", "unsigned int" и "unsigned long int", каждый из который занимает тот же объем памяти и имеет те же требования выравнивания (3.9), что и соответствующий целочисленный тип со знаком;...
Заполнение может произойти из-за "требований выравнивания реализации":
9.2 Члены класса [class.mem]
9.2/12 Нестатические члены данных (не объединяющего) класса, объявленные без промежуточного спецификатора доступа, распределяются таким образом, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных, разделенных спецификатором доступа, не определен (11.1). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).
Имеет ли слово "распределенный" в 9.2/12 то же значение, что и "распределенный" в 3.9/5? Большинство применений "распределенных" в спецификации относятся к динамическому распределению памяти, а не к структуре внутри структуры. Использование may в 9.2/12, по-видимому, подразумевает, что требования выравнивания 3.9/5 и 3.9.1/3 могут не требоваться строго для членов структуры.
Первый член POD-структуры будет выровнен в соответствии с требованием выравнивания структуры:
9.2 / 17 Указатель на объект POD-struct, соответствующим образом преобразованный с использованием reinterpret_cast, указывает на его начальный элемент (или, если этот элемент является битовым полем, то на модуль, в котором он находится), и наоборот. [Примечание: Следовательно, в объекте POD-struct может быть безымянный отступ, но не в его начале, что необходимо для достижения соответствующего выравнивания. ]
[Акцент добавлен во всех приведенных выше цитатах.]
2 ответа
Каждый элемент структуры POD сам по себе является объектом, и объекты могут быть размещены только в соответствии с требованиями выравнивания для этих объектов. Однако требования к выравниванию могут измениться из-за того, что что-то является подобъектом другого объекта ([basic.align] / 1, 2:
1 Типы объектов имеют требования к выравниванию (3.9.1, 3.9.2), которые накладывают ограничения на адреса, по которым может быть выделен объект этого типа. Выравнивание - это определенное реализацией целочисленное значение, представляющее число байтов между последовательными адресами, по которым данный объект может быть выделен. Тип объекта накладывает требование выравнивания на каждый объект этого типа; Более строгое выравнивание может быть запрошено с использованием спецификатора выравнивания (7.6.2).
2 Фундаментальное выравнивание представлено выравниванием, меньшим или равным наибольшему выравниванию, поддерживаемому реализацией во всех контекстах, которое равно
alignof(std::max_align_t)
(18.2). Выравнивание, требуемое для типа, может отличаться, когда он используется как тип законченного объекта и когда он используется как тип подобъекта. [ Пример:struct B { long double d; }; struct D : virtual B { char c; }
когда
D
тип завершенного объекта, он будет иметь подобъект типаB
, поэтому он должен быть выровнен соответствующим образом дляlong double
, ЕслиD
появляется как подобъект другого объекта, который также имеетB
как виртуальный базовый класс,B
подобъект может быть частью другого подобъекта, что снижает требования кD
подобъект.— конец примера ] Результатalignof
Оператор отражает требование выравнивания типа в случае полного объекта.
[выделение добавлено]
Хотя примеры ссылаются на подобъект посредством наследования, нормативная формулировка в целом относится только к подобъектам, поэтому я считаю, что применяются те же правила, поэтому, с одной стороны, можно предположить, что каждый подобъект выровнен так, что он может быть доступным. С другой стороны, нет, вы не можете обязательно предполагать, что это будет такое же выравнивание, что alignof
дает тебе.
[Ссылка с N4296, но я считаю, что то же самое относится ко всем последним версиям. С ++98/03, конечно, не было alignof
вообще, но я считаю, что применяется тот же основной принцип - элементы будут выровнены, чтобы их можно было использовать, но это требование выравнивания не обязательно совпадает с тем, когда они используются в качестве независимых объектов.]
Для каждого типа есть два разных требования к выравниванию. Один соответствует завершенным объектам, другой - подобъектам. [Basic.align]/2:
Выравнивание, требуемое для типа, может отличаться, когда он используется как тип законченного объекта и когда он используется как тип подобъекта. [ Пример:
struct B { long double d; }; struct D : virtual B { char c; };
когда
D
тип завершенного объекта, он будет иметь подобъект типаB
, поэтому он должен быть выровнен соответствующим образом дляlong double
, ЕслиD
появляется как подобъект другого объекта, который также имеет B в качестве виртуального базового класса,B
подобъект может быть частью другого подобъекта, что снижает требования кD
субобъект. - конец примера ] Результатalignof
Оператор отражает требование выравнивания типа в случае полного объекта.
Я не могу придумать какого-либо конкретного примера для подобъектов-членов - и, конечно же, их нет! -, но вышеприведенный абзац допускает возможность того, что подобъект члена имеет более слабое выравнивание, чем alignof
уступит, что подведет ваше состояние.