Что такое производительность, безопасность и выравнивание члена Data, скрытого во встроенном массиве символов в классе C ++? - PullRequest
2 голосов
/ 29 октября 2008

Недавно я видел кодовую базу, которая, боюсь, нарушает ограничения выравнивания. Я вычистил его, чтобы привести минимальный пример, приведенный ниже. Вкратце, игроками являются:

  • Бассейн . Это класс, который эффективно распределяет память, для некоторого определения «работоспособный». Пул гарантированно возвращает часть памяти, выровненную для запрошенного размера.

  • Obj_list . Этот класс хранит однородные коллекции объектов. Как только число объектов превышает определенный порог, оно меняет свое внутреннее представление со списка на дерево. Размер Obj_list равен одному указателю (8 байт на 64-битной платформе). Его заполненный магазин, конечно, превысит это.

  • Совокупный . Этот класс представляет собой очень распространенный объект в системе. Его история восходит к ранней эре 32-битных рабочих станций, и она была «оптимизирована» (в ту же 32-битную эру), чтобы в результате использовать как можно меньше места. Агрегат s может быть пустым или управлять произвольным числом объектов.

В этом примере Aggregate элементов всегда выделяются из Pool s, поэтому они всегда выровнены. Единственные вхождения Obj_list в этом примере - это «скрытые» элементы в Агрегированных объектах, и поэтому они всегда размещаются с использованием размещения нового . Вот классы поддержки:

class Pool
{
public:
   Pool();
   virtual ~Pool();
   void *allocate(size_t size);
   static Pool *default_pool();   // returns a global pool
};

class Obj_list
{
public:
   inline void *operator new(size_t s, void * p) { return p; }

   Obj_list(const Args *args);
   // when constructed, Obj_list will allocate representation_p, which
   // can take up much more space.

   ~Obj_list();

private:
   Obj_list_store *representation_p;
};

А вот и Совокупность. Обратите внимание, что объявление члена member_list_store_d :

// Aggregate is derived from Lesser, which is twelve bytes in size
class Aggregate : public Lesser
{
public:
   inline void *operator new(size_t s) {
      return Pool::default_pool->allocate(s);
   }

   inline void *operator new(size_t s, Pool *h) {
      return h->allocate(s);
   }

public:

   Aggregate(const Args *args = NULL);
   virtual ~Aggregate() {};

   inline const Obj_list *member_list_store_p() const;

protected:
   char member_list_store_d[sizeof(Obj_list)];
};

Это тот элемент данных, который меня больше всего беспокоит. Вот псевдокод для инициализации и доступа:

Aggregate::Aggregate(const Args *args)
{
   if (args) {
      new (static_cast<void *>(member_list_store_d)) Obj_list(args);
   }
   else {
      zero_out(member_list_store_d);
   }
}

inline const Obj_list *Aggregate::member_list_store_p() const
{
   return initialized(member_list_store_d) ? (Obj_list *) &member_list_store_d : 0;
}

Может возникнуть желание предложить заменить массив char указателем на тип Obj_list , инициализированный значением NULL или экземпляром класса. Это дает правильную семантику, но просто меняет стоимость памяти. Если бы память все еще была премиальной (а это может быть представление базы данных EDA), замена массива char указателем на Obj_list обойдется еще в один указатель в случае, когда Aggregate объекты do имеют членов.

Кроме того, я не хочу отвлекаться от основного вопроса, который здесь касается выравнивания. Я думаю вышеупомянутая конструкция проблематична, но на самом деле не могу найти больше в стандарте, чем какое-то смутное обсуждение поведения выравнивания 'system / library' new .

Итак, приведенная выше конструкция делает что-то большее, чем случайная остановка трубы?

Редактировать : Я понимаю, что есть способы заменить подход с использованием встроенного массива char. То же самое сделали оригинальные архитекторы. Они отказались от них, потому что память была на высоте. Теперь, если у меня есть причина прикоснуться к этому коду, я, вероятно, изменю его.

Однако мой вопрос о проблемах выравнивания, присущих этому подходу, - это то, на что, я надеюсь, люди обратятся. Спасибо!

Ответы [ 5 ]

2 голосов
/ 30 октября 2008

Хорошо - был шанс прочитать это правильно. У вас есть проблема с выравниванием, и вы вызываете неопределенное поведение, когда вы получаете доступ к массиву char как Obj_list. Скорее всего, ваша платформа выполнит одно из трех действий: пусть вам это сойдет с рук, пусть это сойдет вам с рук при штрафных санкциях во время выполнения или изредка вылетает из-за ошибки шины.

Ваши портативные опции для исправления:

  • выделить хранилище с помощью malloc или глобальная функция распределения, но ты думаешь это тоже дорогой.
  • как говорит Аркадий, сделайте ваш буфер членом Obj_list:

    Obj_list list;
    

но теперь вы не хотите оплачивать стоимость строительства. Вы могли бы смягчить это, предоставив встроенный неиспользуемый конструктор, который будет использоваться только для создания этого экземпляра - как это сделал бы конструктор по умолчанию. Если вы следуете по этому пути, настоятельно рекомендуем вызвать dtor

list.~Obj_list();

перед размещением нового в этом хранилище.

В противном случае, я думаю, что у вас остались непереносимые параметры: либо полагайтесь на допуск не выровненных обращений вашей платформы, либо используйте любые непереносимые параметры, предоставляемые вашим компилятором.

Отказ от ответственности: вполне возможно, что мне не хватает трюка с профсоюзами или чем-то подобным. Это необычная проблема.

1 голос
/ 30 октября 2008

Можете ли вы просто иметь экземпляр Obj_list внутри Aggregate? IOW, что-то вроде

класс Агрегат: публичный Малый { ... защищенный: Список Obj_list; };

Должно быть, я что-то упустил, но не могу понять, почему это плохо.

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

1 голос
/ 30 октября 2008

Если вы хотите обеспечить выравнивание ваших структур, просто сделайте

// MSVC
#pragma pack(push,1)

// structure definitions

#pragma pack(pop)

// *nix
struct YourStruct
{
    ....
} __attribute__((packed));

Чтобы обеспечить 1-байтовое выравнивание вашего массива символов в Aggregate

1 голос
/ 29 октября 2008

Выравнивание будет выбрано компилятором в соответствии с его настройками по умолчанию, это, вероятно, в конечном итоге составит четыре байта в GCC / MSVC.

Это должно быть проблемой, только если есть код (SIMD / DMA), который требует определенного выравнивания. В этом случае вы должны иметь возможность использовать директивы компилятора, чтобы гарантировать выравнивание member_list_store_d, или увеличить размер на (alignment-1) и использовать соответствующее смещение.

0 голосов
/ 30 октября 2008

Выделите массив char member_list_store_d с помощью malloc или глобального оператора new [], любой из которых предоставит память для любого типа.

Edit: просто прочитайте OP снова - вы не хотите платить за другой указатель. Буду читать снова утром.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...