Порядок хранения внутри строения / объекта
Рассмотрим эти два случая:
struct customType
{
dataType1 var1;
dataType2 var2;
dataType3 var3;
} ;
customType instance1;
// Assume var1, var2 and var3 were initialized to some valid values.
customType * instance2 = &instance1;
dataType1 firstMemberInsideStruct = (dataType1)(*instance2);
class CustomType
{
public:
dataType1 member1;
dataType2 member2;
retrunType1 memberFunction1();
private:
dataType3 member3;
dataType4 member4;
retrunType2 memberFunction2();
};
customType object;
// Assume member1, member2, member3 and member4 were initialized to some valid values.
customType *pointerToAnObject = &object ;
dataType1 firstMemberInTheObject = (dataType1) (*pointerToAnObject);
Всегда ли это безопасно?
Я хочу знать, определяет ли стандарт какой-либо порядок хранения среди -
- Элементы внутри структуры C.
- Члены данных внутри объекта класса C++.
4 ответа
C99 и C++ немного отличаются по этому вопросу.
Стандарт C99 гарантирует, что поля структуры будут размещены в памяти в том порядке, в котором они объявлены, и что поля двух идентичных структур будут иметь одинаковые смещения. См. Этот вопрос для соответствующих разделов стандарта C99. Подводя итог: смещение первого поля задано равным нулю, но смещения после этого не определены стандартом. Это позволяет компиляторам C корректировать смещения каждого поля, чтобы поле удовлетворяло любым требованиям выравнивания памяти архитектуры. Поскольку это зависит от реализации, C предоставляет стандартный способ определения смещения каждого поля, используя offsetof
макро.
C++ предлагает эту гарантию только для простых старых данных (POD). Классы C++, которые не являются простыми старыми данными, не могут обрабатываться следующим образом. Стандарт предоставляет компилятору C++ некоторую свободу в организации класса, когда класс использует множественное наследование, имеет непубличные поля или члены или содержит виртуальные члены.
Что это значит для ваших примеров:
dataType1 firstMemberInsideStruct = (dataType1)(*instance2);
Эта строка в порядке, только если dataType1, dataType2 и dataType3 являются обычными старыми данными. Если ни одного из них нет, то структура customType может не иметь тривиального конструктора (или деструктора), и это предположение может не выполняться.
dataType1 firstMemberInTheObject = (dataType1) (*pointerToAnObject);
Эта линия не является безопасной, независимо от того, dataType1
, dataType2
, а также dataType3
POD, потому что CustomType
класс имеет частные переменные экземпляра. Это делает его не POD-классом, и поэтому вы не можете предполагать, что его первая переменная экземпляра будет упорядочена определенным образом.
9.0.7
Класс стандартного макета - это класс, который: - не имеет нестатических членов-данных типа нестандартного класса макета (или массива таких типов) или ссылки, - не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1), - имеет одинаковый контроль доступа (пункт 11) для всех нестатических элементов данных, - не имеет базовых классов нестандартной компоновки, - либо не имеет нестатических элементов данных в самом производном классе, но не более одного базовый класс с нестатическими элементами данных или не имеет базовых классов с нестатическими элементами данных, и - не имеет базовых классов того же типа, что и первый элемент не статических данных.108
9.2.14
Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа (раздел 11) распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных с различным контролем доступа не определен (11). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).
9.2.20
Указатель на объект структуры стандартной компоновки, соответствующим образом преобразованный с использованием reinterpret_cast, указывает на его начальный элемент (или, если этот элемент является битовым полем, то на модуль, в котором он находится), и наоборот. [Примечание: Следовательно, в объекте структуры стандартной компоновки может быть безымянный отступ, но не в его начале, что необходимо для достижения соответствующего выравнивания. - конец примечания]
Это не всегда безопасно. Если у классов есть virtual
методы, это определенно нет. Члены данных гарантированно отображаются в том же порядке для одного и того же блока уровня доступа, но эти группы могут быть переупорядочены.
Чтобы быть в безопасности при такого рода приведениях, вы должны предоставить конструктор преобразования или оператор приведения, а не полагаться на детали реализации.
Обычно в структуре C члены хранятся в том порядке, в котором они объявлены. Однако элементы должны быть правильно выровнены. В Википедии есть хороший пример того, как это работает.
Я повторю здесь:
Если у вас есть следующая структура
struct MixedData
{
char Data1;
short Data2;
int Data3;
char Data4;
};
Заполнение будет вставлено между различными типами данных, чтобы обеспечить правильное выравнивание байтов. char
s выровнены по 1 байту, short
s выровнены по 2 байта, int
s выровнены по 4 байта и т. д.
Таким образом, чтобы сделать Data2
Выровненный по 2 байта, между 1-байтовым заполнением будет вставлено Data1
а также Data2
,
Стоит также отметить, что существуют механизмы, которые могут изменить выравнивание упаковки. Смотрите #pragma pack.