Я перегружаюсь operator new
, но я недавно поразил проблему с выравниванием. В основном у меня есть класс IBase
который обеспечивает operator new
и delete
во всех необходимых вариантах. Все классы происходят из IBase
и следовательно также используйте пользовательские средства выделения.
Проблема, с которой я сталкиваюсь теперь, состоит в том, что у меня есть ребенок Foo
который должен составить выровненных 16 байтов, в то время как все другие в порядке при выравнивании к 8 байтам. Мое средство выделения памяти однако выравнивается к 8-байтовым границам только по умолчанию, поэтому теперь код в IBase::operator new
возвращает неприменимую часть памяти. Как это, как предполагается, решено правильно?
Я могу просто вызвать все выделения к 16 байтам, которые будут хорошо работать, пока 32 байта не выровнялись, тип открывается. Выяснение выравнивания внутри operator new
кажется, не тривиален (я могу сделать вызов виртуальной функции там для получения фактического выравнивания?), Что рекомендуемый путь состоит в том, чтобы обработать это?
Я знаю malloc
как предполагается, возвращает часть памяти, которая является соответственно выровненная для всего, к сожалению, это "все" не включает типы SSE, и я действительно хотел бы получить эту работу, не требуя, чтобы пользователь помнил, который тип имеет который выравнивание.
Это возможное решение. Он всегда будет выбирать оператор с наивысшим выравниванием в данной иерархии:
#include <exception>
#include <iostream>
#include <cstdlib>
// provides operators for any alignment >= 4 bytes
template<int Alignment>
struct DeAllocator;
template<int Alignment>
struct DeAllocator : virtual DeAllocator<Alignment/2> {
void *operator new(size_t s) throw (std::bad_alloc) {
std::cerr << "alignment: " << Alignment << "\n";
return ::operator new(s);
}
void operator delete(void *p) {
::operator delete(p);
}
};
template<>
struct DeAllocator<2> { };
// ........... Test .............
// different classes needing different alignments
struct Align8 : virtual DeAllocator<8> { };
struct Align16 : Align8, virtual DeAllocator<16> { };
struct DontCare : Align16, virtual DeAllocator<4> { };
int main() {
delete new Align8; // alignment: 8
delete new Align16; // alignment: 16
delete new DontCare; // alignment: 16
}
Он основан на правиле доминирования: если есть неоднозначность в поиске, и неоднозначность существует между именами производного и виртуального базового класса, имя производного класса.
Возникли вопросы, почему DeAllocator
наследует DeAllocator
. Ответ заключается в том, что в данной иерархии могут быть разные требования к выравниванию, налагаемые классами. Представьте, что IBase
не имеет требований к выравниванию, A
требует 8 байтов, а B
требует 16 байтов и наследует A
:
class IBAse { };
class A : IBase, Alignment<8> { };
class B : A, Alignment<16> { };
Выравнивание <16>
и Выравнивание <8>
оба предоставляют оператор new
.Если вы теперь скажете new B
, компилятор будет искать operator new
в B
и найдет две функции:
// op new
Alignment<8> IBase
^ /
\ /
\ /
// op new \ /
Alignment<16> A
\ /
\ /
\ /
B
B -> Alignment<16> -> operator new
B -> A -> Alignment<8> -> operator new
Таким образом , это было бы неоднозначным , и мы не смогли бы скомпилировать: Ни один из них не скрывает другого. Но если теперь вы наследуете Alignment <16>
виртуально от Alignment <8>
и сделаете A
и B
наследовать их виртуально, то оператор new
в Выравнивание <8>
будет скрыто:
// op new
Alignment<8> IBase
^ /
/ \ /
/ \ /
// op new / \ /
Alignment<16> A
\ /
\ /
\ /
B
Это специальное правило скрытия (также называемое правилом доминирования ), однако работает, только если all Объекты Alignment <8>
совпадают. Таким образом, мы всегда наследуем виртуально: в этом случае существует только один Alignment <8>
(или 16, ...) объект, существующий в любой данной иерархии классов.
миксины - правильный подход, однако перегрузка оператора new - нет. Это позволит достичь того, что вам нужно:
__declspec(align(256)) struct cachealign{};
__declspec(align(4096)) struct pagealign{};
struct DefaultAlign{};
struct CacheAlign:private cachealign{};
struct PageAlign: CacheAlign,private pagealign{};
void foo(){
DefaultAlign d;
CacheAlign c;
PageAlign p;
std::cout<<"Alignment of d "<<__alignof(d)<<std::endl;
std::cout<<"Alignment of c "<<__alignof(c)<<std::endl;
std::cout<<"Alignment of p "<<__alignof(p)<<std::endl;
}
Prints
Alignment of d 1
Alignment of c 256
Alignment of p 4096
Для gcc используйте
struct cachealign{}__attribute__ ((aligned (256)));
Обратите внимание, что происходит автоматический выбор наибольшего выравнивания, и это работает для объектов, помещенных в стек, тех, которые new'd, и как члены других классов. Это также не добавляет никаких виртуалов и, предполагая EBCO, не увеличивает размер класса (кроме вставки, необходимой для самого выравнивания).