I'm overloading operator new
, but I recently hit a problem with alignment. Basically, I have a class IBase
which provides operator new
and delete
in all required variants. All classes derive from IBase
and hence also use the custom allocators.
The problem I'm facing now is that I have a child Foo
which has to be 16-byte aligned, while all others are fine when aligned to 8-byte. My memory allocator however aligns to 8-byte boundaries only by default, so now the code in IBase::operator new
returns an unusable piece of memory. How is this supposed to be solved correctly?
I can simply force all allocations to 16 bytes, which will work fine until a 32-byte aligned type pops up. Figuring out the alignment inside operator new
doesn't seem to be trivial (can I do a virtual function call there to obtain the actual alignment?) What's the recommended way to handle this?
I know malloc
is supposed to return a piece of memory which is suitably aligned for everything, unfortunately, this "everything" doesn't include SSE types and I'd really like to get this working without requiring the user to remember which type has which alignment.
New and Delete operators can be overloaded globally or they can be overloaded for specific classes. If these operators are overloaded using member function for a class, it means that these operators are overloaded only for that specific class.
The operator new (or better the void* operator new(size_t) variant) just allocate memory, but does not do any object construction. The new keyword calls the operator new function, but then calls the object constructor.
When new is used to allocate memory for a C++ class object, the object's constructor is called after the memory is allocated. Use the delete operator to deallocate the memory allocated by the new operator.
In C++, we can change the way operators work for user-defined types like objects and structures. This is known as operator overloading. For example, Suppose we have created three objects c1 , c2 and result from a class named Complex that represents complex numbers.
This is a possible solution. It will always choose the operator with the highest alignment in a given hierarchy:
#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 }
It's based on the dominance rule: If there is an ambiguity in lookup, and the ambiguity is between names of a derived and a virtual base class, the name of the derived class is taken instead.
Questions were risen why DeAllocator<I>
inherits DeAllocator<I / 2>
. The answer is because in a given hierarchy, there may be different alignment requirements imposed by classes. Imagine that IBase
has no alignment requirements, A
has 8 byte requirement and B
has 16 byte requirement and inherits A
:
class IBAse { }; class A : IBase, Alignment<8> { }; class B : A, Alignment<16> { };
Alignment<16>
and Alignment<8>
both expose an operator new
. If you now say new B
, the compiler will look for operator new
in B
and will find two functions:
// op new Alignment<8> IBase ^ / \ / \ / // op new \ / Alignment<16> A \ / \ / \ / B B -> Alignment<16> -> operator new B -> A -> Alignment<8> -> operator new
Thus, this would be ambiguous and we would fail to compile: Neither of these hide the other one. But if you now inherit Alignment<16>
virtually from Alignment<8>
and make A
and B
inherit them virtually, the operator new
in Alignment<8>
will be hidden:
// op new Alignment<8> IBase ^ / / \ / / \ / // op new / \ / Alignment<16> A \ / \ / \ / B
This special hiding rule (also called dominance rule) however only works if all Alignment<8>
objects are the same. Thus we always inherit virtually: In that case, there is only one Alignment<8>
(or 16, ...) object existing in any given class hierarchy.
mixins are the right approach, however overloading operator new is not. This will accomplish what you need:
__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
For gcc, use
struct cachealign{}__attribute__ ((aligned (256)));
Note that there is automatic selection of the largest alignment, and this works for objects placed on the stack, ones that are new'd, and as members of other classes. Nor does it add any virtuals and assuming EBCO, no extra size to the class (outside of the padding needed for the alignment itself).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With