Какова производительность, безопасность и выравнивание элемента данных, скрытого во встроенном массиве символов в классе C ++?

StackOverflow https://stackoverflow.com/questions/247639

  •  05-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

В этом примере, Совокупный элементы всегда выделяются из Бассейн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 Список объектов будет стоить еще один указатель в случае, когда Совокупный Объекты делай иметь членов.

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

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

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

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

Это было полезно?

Решение

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

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

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

    Obj_list list;
    

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

list.~Obj_list();

прежде чем выполнять новое размещение в этом хранилище.

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

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

Другие советы

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

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

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

совокупный класс :общественный Меньший { ...защищенный:Список Obj_list;};

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

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

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

// MSVC
#pragma pack(push,1)

// structure definitions

#pragma pack(pop)

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

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

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

Редактировать:Просто прочтите ОП еще раз - вы же не хотите платить за другой указатель.Прочитаю еще раз утром.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top