Недавно я увидел кодовую базу, которая, как я боюсь, нарушает ограничения выравнивания. Я очистил его, чтобы получить минимальный пример, приведенный ниже. Вкратце, игроки:
Бассейн. Это класс, который эффективно распределяет память для некоторого определения «эффективного». Бассейн гарантированно вернет кусок памяти, который выровнен по запрошенному размеру.
Obj_list. Этот класс хранит однородные коллекции объектов. Как только количество объектов превышает определенный порог, он меняет свое внутреннее представление со списка на дерево. Размер Obj_list - один указатель (8 байтов на 64-битной платформе). Его густонаселенный магазин, конечно, превзойдет это.
Совокупный. Этот класс представляет собой очень распространенный объект в системе. Его история восходит к ранней эре 32-битных рабочих станций, и в результате он был «оптимизирован» (в ту же 32-битную эпоху), чтобы использовать как можно меньше места. Совокупный может быть пустым или управлять произвольным количеством объектов.
В этом примере элементы Совокупный всегда выделяются из Бассейн, поэтому они всегда выровнены. Единственные вхождения 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;
}
У вас может возникнуть соблазн предложить заменить массив символов указателем на тип Obj_list, инициализированный значением NULL или экземпляром класса. Это дает правильную семантику, но просто меняет стоимость памяти. Если бы память все еще была в дефиците (а это могло быть, это представление базы данных EDA), замена массива char указателем на Obj_list потребовала бы еще одного указателя в случае, когда объекты Совокупныйделать имеют члены.
Кроме того, я не хочу отвлекаться от главного вопроса, а именно от выравнивания. Я считать вышеупомянутая конструкция проблематична, но я не могу найти больше в стандарте, чем какое-то расплывчатое обсуждение поведения выравнивания «системы / библиотеки» новый.
Итак, делает ли вышеупомянутая конструкция больше, чем вызывает периодическое заклинивание трубы?
Редактировать: Я понимаю, что есть способы заменять подхода с использованием встроенного массива символов. Так поступили и первоначальные архитекторы. Они отказались от них, потому что память была в большом почете. Теперь, если у меня есть причина прикоснуться к этому коду, я, вероятно, его изменю.
Однако я надеюсь, что люди обратятся к моему вопросу о проблемах согласования, присущих этому подходу. Спасибо!





Выравнивание будет выбрано компилятором в соответствии с его настройками по умолчанию, это, вероятно, будет иметь четыре байта в GCC / MSVC.
Это должно быть проблемой только при наличии кода (SIMD / DMA), который требует определенного выравнивания. В этом случае вы должны иметь возможность использовать директивы компилятора, чтобы гарантировать, что member_list_store_d выровнено, или увеличить размер на (alignment-1) и использовать соответствующее смещение.
Выделите массив символов member_list_store_d с помощью malloc или глобального оператора new [], каждый из которых предоставит память, выровненную для любого типа.
Обновлено: просто прочитайте OP еще раз - вы не хотите платить за другой указатель. Буду читать утром еще раз.
Думаю, я не понимаю. Массив символов member_list_store_d является переменной-членом Совокупный. Мы используем размещение new в в этом массиве символов для создания объекта Obj_list. Что вы предлагаете сделать с помощью malloc или глобального оператора new [] для этого существующего элемента данных массива?
Как член данных, массив char не обязательно имеет какое-либо конкретное выравнивание. Вместо этого вы можете сделать member_list_store_d char * и выделить его с помощью malloc (sizeof (Obj_list)) или new char [sizeof (Obj_list)] в ctor агрегата. Это дает вам гарантию выравнивания с минимальными нарушениями.
Обратите внимание, что строго глобальный оператор new [] для char * гарантирует выравнивание только для типов, которые достаточно малы, чтобы поместиться в выделенный вами массив. Этого достаточно для всех практических целей (поэтому все это гарантировано). Но это действительно означает, что «new char [2]» не обязательно 4-выровнен.
... в отличие от malloc (2). Предполагая платформу, где существует тип, требующий выравнивания по четырем точкам.
У вас есть ссылка? Я смотрю на 3.7.3.1 «Возвращаемый указатель должен быть соответствующим образом выровнен, чтобы его можно было преобразовать в указатель любого полного типа объекта, а затем использовать для доступа к объекту или массиву в выделенном хранилище». Но это большой документ - я мог что-то упустить.
Может ли быть правило «как если» - соответствующая программа не может сказать, что она не согласована?
Если вы хотите обеспечить выравнивание ваших структур, просто выполните
// MSVC
#pragma pack(push,1)
// structure definitions
#pragma pack(pop)
// *nix
struct YourStruct
{
....
} __attribute__((packed));
Чтобы обеспечить 1-байтовое выравнивание вашего массива символов в Aggregate
Это программное обеспечение работает в основном в Unix / Linux. Доступна ли эта прагма для компиляторов, предназначенных для * nix?
Можете ли вы просто иметь экземпляр Obj_list внутри Aggregate? IOW, что-то вроде
class Aggregate: публичный Меньший { ... защищено: Obj_list list; };
Должно быть, я чего-то упускаю, но не могу понять, почему это плохо.
Что касается вашего вопроса - он полностью зависит от компилятора. Однако большинство компиляторов выравнивают каждый член по границе слова по умолчанию, даже если нет необходимости выравнивать тип члена таким образом для правильного доступа.
Как опубликовано в другом месте @ fizzer.myopen.com, этот подход был явно отвергнут первоначальными архитекторами, поскольку при создании объекта Obj_list потребовался бы дополнительный указатель.
Как так? В вопросе говорилось, что «замена массива char указателем на Obj_list будет стоить еще один указатель в случае, когда у объектов Aggregate есть члены». Я говорю о пример Obj_list, а не об указателе.
Извините, я неправильно понял. Таким образом, Obj_list (как реализовано) создаст объект коллекции во вторичном хранилище. Массив используется, чтобы избежать его построения, если объекты не предоставлены. Опять же, я считаю это решение разумным, но вопрос заключается в согласовании этой конструкции, а не в альтернативах.
О, я думаю, мой ответ на вопрос о выравнивании довольно очевиден. Только не полагайся на это. И я пытаюсь показать, как избавиться от зависимости. Возможный конструктор для Obj_list установил бы pimpl в NULL и дождался первой вставки, прежде чем делать что-либо еще.
@Arkadiy, спасибо за вклад. Я уже думал о заменах, включая ту, о которой вы только что упомянули. Эта тема предназначена только для обсуждения значения описанной «идиомы».
Хорошо - успел прочитать как следует. У вас проблема с выравниванием, и вы вызываете неопределенное поведение при доступе к массиву char как Obj_list. Скорее всего, ваша платформа выполнит одно из трех действий: позволит вам избежать неприятностей, позволит вам избежать наказания за время выполнения или иногда выйдет из строя из-за ошибки шины.
Ваши портативные варианты исправить это:
как говорит Аркадий, сделайте свой буфер членом Obj_list:
Obj_list list;
но теперь вы не хотите оплачивать стоимость строительства. Вы можете смягчить это, предоставив встроенный конструктор, который ничего не делает, который будет использоваться только для создания этого экземпляра - как указано в конструкторе по умолчанию. Если вы следуете этому маршруту, настоятельно рекомендуем вызвать dtor.
list.~Obj_list();
перед размещением нового в этом хранилище.
В противном случае, я думаю, у вас останутся непереносимые параметры: либо полагайтесь на устойчивость вашей платформы к несогласованным доступам, либо используйте любые непереносимые параметры, которые дает вам ваш компилятор.
Отказ от ответственности: вполне возможно, что я упускаю хитрость с профсоюзами или чем-то подобным. Это необычная проблема.
Меня это беспокоит с тех пор, как я наткнулся на него. Он присутствует в коде перевозки почти двадцать лет, но я, по-прежнему, чувствую желание изменить его на один из вариантов, которые вы (и я, и другие) перечислили. Есть какой-нибудь ответ на первое сообщение @ Andrew? Похоже, он подразумевает, что эта «идиома» «безопасна».
Я не могу прочитать эту интерпретацию в его сообщении: он говорит, что это может быть проблемой на некоторых платформах. Если код проработал без происшествий 20 лет, ваша платформа явно не входит в их число. Неопределенное поведение просто не определено. Программа не терпит краха.
- Агрегат выравнивается по пулу. - Агрегат происходит от Меньшего, что составляет двенадцать байтов. - member_list_store_d - массив символов в Aggregate. Вы хотите сказать, что компилятор выровняет этот элемент массива char по четырем байтам? Если да, то, предположительно, на 64-битной платформе он будет выровнен по восьми байтам?