防止堆上的未对齐数据

时间:2010-06-22 18:12:20

标签: c++ visual-c++ alignment

我正在构建一个使用SSE内在函数的类层次结构,因此该类的一些成员需要进行16字节对齐。对于堆栈实例,我可以使用__declspec(align(#)),如下所示:

typedef __declspec(align(16)) float Vector[4];
class MyClass{
...
private:
Vector v;
};

现在,由于__declspec(align(#))是编译指令,因此以下代码可能会导致堆上未对齐的Vector实例:

MyClass *myclass = new MyClass;

这也是,我知道我可以通过重载 new delete 运算符轻松解决,以便相应地使用_aligned_malloc_aligned_free。像这样:

//inside MyClass:
public:
void* operator new (size_t size) throw (std::bad_alloc){
    void * p = _aligned_malloc(size, 16);
    if (p == 0)  throw std::bad_alloc()
    return p; 
}

void operator delete (void *p){
    MyClass* pc = static_cast<MyClass*>(p); 
    _aligned_free(p);
}
...

到目前为止一直很好..但这是我的问题。请考虑以下代码:

class NotMyClass{ //Not my code, which I have little or no influence over
...
MyClass myclass;
...
};
int main(){
    ...
    NotMyClass *nmc = new NotMyClass;
    ...
}

由于MyClass的myclass实例是在NotMyClass的动态实例上静态创建的,因为Vector的__declspec(align(16))指令,myclass将相对于nmc的开头16字节对齐。但这是毫无价值的,因为nmc是使用NotMyClass的 new 运算符在堆上动态分配的,它不会确保(并且绝对可能不是)16字节对齐。

到目前为止,我只能想到如何处理这个问题的两种方法:

  1. 阻止MyClass用户编译以下代码:

    MyClass myclass;
    

    意思是,MyClass的实例只能使用new运算符动态创建,从而确保MyClass的所有实例都真正动态分配MyClass的重载new。我已就另一个关于如何实现这一点的问题咨询并得到了一些很好的答案: C++, preventing class instance from being created on the stack (during compiltaion)

  2. 从我的班级中拥有Vector成员,只有指向Vector的成员,我将分别使用_aligned_malloc_aligned_free分配和解除分配。这个方法似乎很粗糙,容易出错,因为我不是编写这些类的唯一程序员(MyClass派生自Base类,其中许多类使用SSE)。

  3. 然而,由于我的团队对这两种解决方案都不满意,我来找你提出不同解决方案的建议。

3 个答案:

答案 0 :(得分:4)

如果你设置了堆分配,另一个想法是在堆栈上过度分配并手动对齐(手动对齐在this SO post中讨论)。我们的想法是分配字节数据(unsigned char),其大小保证包含必要大小(+15)的对齐区域,然后通过从最移位区域向下舍入找到对齐位置( x+15 - (x+15) % 16x+15 & ~0x0F)。我在codepadg++ -O2 -msse2)上使用向量操作发布了这种方法的工作示例。以下是重点:

class MyClass{
   ...
   unsigned char dPtr[sizeof(float)*4+15]; //over-allocated data
   float* vPtr;                            //float ptr to be aligned

   public:
      MyClass(void) : 
         vPtr( reinterpret_cast<float*>( 
            (reinterpret_cast<uintptr_t>(dPtr)+15) & ~ 0x0F
         ) ) 
      {}
   ...
};
...

构造函数确保vPtr对齐(注意类声明中成员的顺序很重要)。

这种方法有效(包含类的堆/堆栈分配与对齐无关),是portabl-ish(我认为大多数编译器提供指针大小的uint uintptr_t),并且不会泄漏内存。但它不是特别安全(确保将对齐的指针保持在复制等下有效),浪费(几乎)它使用的内存,有些人可能会发现reinterpret_casts令人反感。

通过将此逻辑封装在Vector对象中,可以大大消除对齐操作/未对齐数据问题的风险,从而控制对对齐指针的访问,并确保它在构造时对齐并保持有效。

答案 1 :(得分:1)

您可以使用“placement new。”

void* operator new(size_t, void* p) { return p; }

int main() {
    void* p = aligned_alloc(sizeof(NotMyClass));
    NotMyClass* nmc = new (p) NotMyClass;
    // ...

    nmc->~NotMyClass();
    aligned_free(p);
}

当然,在销毁对象时需要注意,通过调用析构函数然后释放空间。你不能只是打电话给删除。你可以使用shared_ptr&lt;&gt;具有不同的功能来自动处理;这取决于处理shared_ptr(或指针的其他包装器)的开销是否是一个问题。

答案 2 :(得分:0)

即将推出的C ++ 0x标准提出了处理原始内存的工具。它们已经包含在VC ++ 2010中(在tr1命名空间内)。

std::tr1::alignment_of // get the alignment
std::tr1::aligned_storage // get aligned storage of required dimension

这些是类型,您可以这样使用它们:

static const floatalign = std::tr1::alignment_of<float>::value; // demo only

typedef std::tr1::aligned_storage<sizeof(float)*4, 16>::type raw_vector;
        // first parameter is size, second is desired alignment

然后你可以申报你的班级:

class MyClass
{
public:

private:
  raw_vector mVector; // alignment guaranteed
};

最后,你需要一些演员来操纵它(直到现在才是原始内存):

float* MyClass::AccessVector()
{
  return reinterpret_cast<float*>((void*)&mVector));
}