views:

513

answers:

2

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.

+6  A: 

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.

Johannes Schaub - litb
Nice use of templates and I like the way you think, but I must be missing somethign obvious since I don't see how the alignment is being done. It is assumed that the structs would be packed using the techniques provided by whatever compiler the OP is using?
John Dibling
@John, in this sample, alignment is indicated by printing its value. You would pass the integer to `posix_memalign` or something. @Anteru will have figured out a way to do that already, i suspect.
Johannes Schaub - litb
I don't see how alignment is obtained here. Possibly you are not really implementing alignment here (just showing how you could use templates to indicate a possible alignment size), but in that case, wouldn't you obtain the same with a normal templated class (not inheriting from another templated class), where Align16 inherits from DeAllocator<16> and Align8 inherits from DeAllocator<8> (and no multiple inheritence)?
Patrick
@litb: fair enough, cheers.
John Dibling
@Patrick, the problem then is that there are two `operator new` functions, and you have an ambiguity (of course, i'm assuming `Align16` inherits `Align8`, like `Foo` will inherit `IBase`). The one from `Foo::DeAllocator<16>` or from `IBase::DeAllocator<8>` ?
Johannes Schaub - litb
@litb: Does this solution only work when alignment can be specified at run-time? I don't know of a way to specify alignment at run-time under Windows; only at compile-time.
John Dibling
@litb: Nevermind, figured it out. The secret is _aligned_malloc: http://msdn.microsoft.com/en-us/library/8z34s9c6(VS.80).aspx
John Dibling
This technique seems quite interesting and generic. Is it a known idiom ? For if it's not it could be nice to name.
Matthieu M.
@Matthieu, never heard of it before.
Johannes Schaub - litb
Err, but the problem is, I can't really put the alignment into the parent class, as I don't know the alignment at this point yet.Basically, what I need is instead of having operator new passing only the size, also to get the type (so I can use std::tr1::alignment_of) and pass this along ...
Anteru
@Anteru, you don't need to pass the alignment to the parent class. If a new is done for a type, `DeAllocate<N>` is used for allocation where `N` is the maximal alignment in its hierarchy of the type. Where you specify the alignment doesn't matter. You may do `struct A { }; struct B : A, virtual DeAllocator<16> { }; int main() { new B; }` and it will allocate with `16` bytes.
Johannes Schaub - litb
@Patrick, hope the edit makes it clearer.
Johannes Schaub - litb
@Johannes, +1 for the excellent explanation. Thanks.
Patrick
A: 

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).

Lance Diduck