Недавно я видел кодовую базу, которая, боюсь, нарушает ограничения выравнивания. Я вычистил его, чтобы привести минимальный пример, приведенный ниже. Вкратце, игроками являются:
Бассейн . Это класс, который эффективно распределяет память, для некоторого определения «работоспособный». Пул гарантированно возвращает часть памяти, выровненную для запрошенного размера.
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. То же самое сделали оригинальные архитекторы. Они отказались от них, потому что память была на высоте. Теперь, если у меня есть причина прикоснуться к этому коду, я, вероятно, изменю его.
Однако мой вопрос о проблемах выравнивания, присущих этому подходу, - это то, на что, я надеюсь, люди обратятся. Спасибо!