Макет члена C ++ - PullRequest
       37

Макет члена C ++

6 голосов
/ 18 января 2010

Давайте получим простую структуру (POD).

struct xyz
{
    float x, y, z;
};

Могу ли я считать, что следующий код в порядке? Могу ли я предположить, что нет никаких пробелов? Что говорит стандарт? Это правда для POD? Это правда для классов?

xyz v;
float* p = &v.x;
p[0] = 1.0f;
p[1] = 2.0f; // Is it ok?
p[2] = 3.0f; // Is it ok?

Ответы [ 12 ]

10 голосов
/ 18 января 2010

Ответ здесь немного сложнее. Стандарт C ++ гласит, что типы данных POD будут иметь гарантии совместимости с макетом C ( Reference ). Согласно разделу 9.2 спецификации C члены структуры будут расположены в последовательном порядке, если

  1. Нет разницы в модификаторе доступности
  2. Нет проблем выравнивания с типом данных

Так что да, это решение будет работать до тех пор, пока тип float имеет совместимое выравнивание на текущей платформе (это размер слова платформы). Так что это должно работать для 32-битных процессоров, но я предполагаю, что это не сработает для 64-битных. По сути, везде, где sizeof(void*) отличается от sizeof(float)

5 голосов
/ 18 января 2010

Это не гарантируется стандартом и не будет работать на многих системах.Причины:

  • Компилятор может выравнивать элементы структуры в соответствии с целевой платформой, что может означать 32-разрядное выравнивание, 64-разрядное выравнивание или что-либо еще.
  • размер с плавающей точкой может быть 32 или 64 бита.Нет гарантии, что оно совпадает с выравниванием члена структуры.

Это означает, что p[1] может находиться в том же месте, что и xyz.y, или может частично или совсем не перекрываться.

4 голосов
/ 18 января 2010

Нет, делать это нельзя, кроме первого поля.

Из стандартов C ++:

9,2 Члены класса
Указатель на объект POD-struct, соответствующим образом преобразованы с использованием reinterpret_cast, указывает на его первоначальный член (или если этот член битовое поле, то к единице, в которой оно проживает) и наоборот. [Заметка: Поэтому может быть безымянным заполнение внутри объекта POD-struct, но не в начале, по необходимости добиться соответствующего выравнивания.

2 голосов
/ 18 января 2010

Если есть сомнения, измените структуру данных в соответствии с приложением:

struct xyz
{
    float  p[3];
};  

Для удобства чтения вы можете рассмотреть:

struct xyz
{
    enum { x_index = 0, y_index, z_index, MAX_FLOATS};
    float p[MAX_FLOATS];

    float  X(void) const {return p[x_index];}
    float  X(const float& new_x) {p[x_index] = new_x;}

    float  Y(void) const {return p[y_index];}
    float  Y(const float& new_y) {p[y_index] = new_y;}

    float  Z(void) const {return p[z_index];}
    float  Z(const float& new_z) {p[z_index] = new_z;}
};

Возможно, даже добавить еще инкапсуляцию:

struct Functor
{
  virtual void operator()(const float& f) = 0;
};

struct xyz
{
  void for_each(Functor& ftor)
  {
     ftor(p[0]);
     ftor(p[1]);
     ftor(p[2]);
     return;
  }
  private:
     float p[3];
}

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

2 голосов
/ 18 января 2010

Зависит от оборудования. Стандарт явно позволяет классам POD иметь неопределенное и непредсказуемое заполнение. Я заметил это на странице Википедии C ++ и взял сноску со ссылкой на спецификацию для вас.

^ a ISO / IEC (2003). ISO / IEC 14882: 2003 (E): Языки программирования - C ++ §9.2 Члены класса [class.mem] пункт. 17

В практическом плане, однако, на обычном оборудовании и компиляторах все будет хорошо.

1 голос
/ 03 февраля 2012

Давайте взглянем на исходный код Doom III:

class idVec4 {
public: 
    float           x;
    float           y;
    float           z;
    float           w;
    ...
    const float *   ToFloatPtr( void ) const;
    float *         ToFloatPtr( void );
    ...
}

ID_INLINE const float *idVec4::ToFloatPtr( void ) const {
    return &x;
}

ID_INLINE float *idVec4::ToFloatPtr( void ) {
    return &x;
}

Работает на многих системах.

1 голос
/ 18 января 2010

Нет, вы не можете предполагать, что пробелов нет. Вы можете проверить свою архитектуру, и если ее нет и вам не нужна переносимость, все будет в порядке.

Но представьте себе 64-битную архитектуру с 32-битными числами с плавающей запятой. Компилятор может выровнять float структуры по 64-битным границам, а ваш

p[1]

даст вам мусор, а

p[2]

даст вам то, что вы думаете от

p[1]

& с.

Однако ваш компилятор может дать вам способ упаковать структуру. Это по-прежнему не будет «стандартом» - стандарт не предоставляет ничего подобного, а разные компиляторы предоставляют очень несовместимые способы сделать это - но он, вероятно, будет более переносимым.

1 голос
/ 18 января 2010

Стандарт требует, чтобы порядок расположения в памяти соответствовал порядку определения, но допускает произвольное заполнение между ними. Если у вас есть спецификатор доступа (public:, private: или protected:) между участниками, даже гарантия о заказе теряется.

Редактировать: в конкретном случае, когда все три члена имеют одинаковый примитивный тип (т.е. не сами по себе структуры или что-то в этом роде), у вас есть достаточно шансов - для примитивных типов требования к размеру и выравниванию объекта часто так же, так получается.

OTOH, это только случайно, и имеет тенденцию быть скорее слабостью, чем силой; код неверен, поэтому в идеале он должен сразу же работать, а не работать, вплоть до того дня, когда вы дадите демонстрацию владельцу компании, которая станет вашим самым важным клиентом, и в этот момент это произойдет ( конечно) провалиться самым отвратительным образом ...

0 голосов
/ 16 июля 2012
  1. структура упаковки (например, #pragma pack в MSVC) http://msdn.microsoft.com/en-us/library/aa273913%28v=vs.60%29.aspx
  2. переменное выравнивание (например, __declspec(align( в MSVC) http://msdn.microsoft.com/en-us/library/83ythb65.aspx

являются двумя факторамиэто может разрушить ваши предположения.float обычно имеет ширину 4 байта, поэтому такие большие переменные редко смещаются.Но все еще легко сломать ваш код.

Эта проблема наиболее заметна, когда двоичная структура заголовка чтения с короткими замыканиями (например, BMP или TGA) - забывание pack 1 приводит к катастрофе.

0 голосов
/ 21 июня 2012

Я предполагаю, что вы хотите, чтобы структура сохраняла ваши координаты как элементы (.x, .y и .z), но вы все равно хотите, чтобы к ним обращались, скажем, по OpenGL (как если бы это был массив).

Вы можете попробовать реализовать оператор [] структуры, чтобы к ней можно было обращаться как к массиву. Что-то вроде:

struct xyz
{
  float x, y, z;
  float& operator[] (unsigned int i)
  {
    switch (i)
    {
    case 0:
      return x;
      break;
    case 1:
      return y;
      break;
    case 2:
      return z;
      break;
    default:
      throw std::exception
      break;
    }
  }
};
...