2010-03-02 52 views
31

我超载operator new,但我最近遇到了一个对齐问题。基本上,我有一个类IBase,它提供所有必需的变体operator newdelete。所有类都从IBase派生,因此也使用自定义分配器。运营商新的超载和对齐

我现在面临的问题是,我有一个孩子Foo必须是16字节对齐,而所有其他人都很好,当对齐到8个字节。但是,我的内存分配器仅在默认情况下与8字节边界对齐,因此现在IBase::operator new中的代码返回不可用的内存。这应该如何正确解决?

我可以简单地强制所有分配到16个字节,这将工作正常,直到一个32字节的对齐类型弹出。找出operator new内部的对齐似乎并不重要(我可以在那里做一个虚拟函数调用来获得实际的对齐方式吗?)推荐的方法是什么?

我知道malloc应该返回一段适合所有东西的内存,不幸的是,这个“所有东西”都不包含SSE类型,我真的很想让这个工作不需要用户记住哪种类型具有哪种对齐。

+0

也许你可以为具有特殊对齐要求的少数对象使用不同的IBase(IBase16?)。 – 2010-03-02 21:32:08

+0

你甚至可以使这个基类(IBase16,IBase32)模板化,所以你可以使用IBase 。 – Patrick 2010-03-02 21:35:15

+0

您可以在64字节的边界上对齐。 :) – Bill 2010-03-02 21:42:52

回答

20

这是一个可能的解决方案。它总是选择具有最高对齐操作者在给定的层次:

#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<I>继承DeAllocator<I/2>。答案是因为在给定的层次结构中,类可能会有不同的对齐要求。想像一下,IBase没有对准要求,A有8个字节的要求和B具有16个字节的要求,并且继承A

class IBAse { }; 
class A : IBase, Alignment<8> { }; 
class B : A, Alignment<16> { }; 

Alignment<16>Alignment<8>都暴露一个operator new。如果你现在说new B,编译器会寻找operator newB,并会找到功能:

  // op new 
      Alignment<8>  IBase 
       ^  /
        \  /
        \ /
// op new   \/
Alignment<16>   A 
      \  /
       \ /
       \/
       B 

B ->  Alignment<16> -> operator new 
B -> A -> Alignment<8> -> operator new 

因此,这将是暧昧,我们将无法编译:这些都不隐藏其他一。但是,如果你现在从Alignment<8>继承Alignment<16>实际上并AB继承它们实际上,在Alignment<8>operator new将被隐藏:

  // op new 
      Alignment<8>  IBase 
       ^  /
       /\  /
      / \ /
// op new/  \/
Alignment<16>   A 
      \  /
       \ /
       \/
       B 

这个特殊的隐藏规则(也称为主导地位规则),但只有工作,如果所有的Alignment<8>对象都是一样的。因此,我们总是继承几乎:在这种情况下,在任何给定的类层次结构中存在的只有一个Alignment<8>(或16,...)对象。

+0

不错的使用模板,我喜欢你的想法,但我必须缺少一些明显的迹象,因为我看不到对齐是如何完成的。假设结构将使用OP使用的任何编译器提供的技术进行打包? – 2010-03-02 21:53:14

+0

@John,在这个示例中,通过打印其值来指示对齐。您可以将整数传递给'posix_memalign'或其他东西。我怀疑,@Anteru已经想出了一个办法来做到这一点。 – 2010-03-02 22:00:33

+1

我看不出在这里如何获得对齐。可能你并没有在这里真正实现对齐(只是展示了如何使用模板来指示可能的对齐大小),但是在这种情况下,你不会获得与正常模板类(不是从另一个模板类继承)相同的结果, Align16继承自DeAllocator <16>并且Align8继承自DeAllocator <8>(并且没有多重继承)? – Patrick 2010-03-02 22:00:44

7

mixins是正确的方法,但是重载操作符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; 
} 

打印

Alignment of d 1 
Alignment of c 256 
Alignment of p 4096 

对于GCC,使用

struct cachealign{}__attribute__ ((aligned (256))); 

注意这里是最大的对准的自动选择,这一点也适用于放置的对象在堆栈中,新的,并作为其他类的成员。它也不添加任何虚拟和假设EBCO,没有额外的大小(在对齐本身所需的填充之外)。

1

使用Visual Studio Express的2010年,上面的例子似乎并没有与新的工作:

CacheAlign *c = new CacheAlign; 

会给__align_of(C)= 4(可以预料的我相信),但一个地址CacheAlign的第一个成员不按要求对齐。

不是最近的问题,但是如果我正确理解OP,他的父类的子类定义了分配器&释放器,并且都可以要求特定的对齐。有一个简单的new调用一些专用分配器来完成实际工作并接收一个对齐参数 - 在父类中使用默认对齐方式的继承或重载的指定正确对齐的版本的泛型版本有什么问题?