Порядок хранения внутри строения / объекта

Рассмотрим эти два случая:

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);

Всегда ли это безопасно?

Я хочу знать, определяет ли стандарт какой-либо порядок хранения среди -

  1. Элементы внутри структуры C.
  2. Члены данных внутри объекта класса 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;
};

Заполнение будет вставлено между различными типами данных, чтобы обеспечить правильное выравнивание байтов. chars выровнены по 1 байту, shorts выровнены по 2 байта, ints выровнены по 4 байта и т. д.

Таким образом, чтобы сделать Data2 Выровненный по 2 байта, между 1-байтовым заполнением будет вставлено Data1 а также Data2,

Стоит также отметить, что существуют механизмы, которые могут изменить выравнивание упаковки. Смотрите #pragma pack.

Другие вопросы по тегам