防止堆上的未对齐数据

ela*_*dan 9 c++ alignment visual-c++

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

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

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

MyClass *myclass = new MyClass;
Run Code Online (Sandbox Code Playgroud)

这也是,我知道我可以通过重载新的和删除操作符来轻松解决使用_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);
}
...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

    MyClass myclass;
    
    Run Code Online (Sandbox Code Playgroud)

    意思是,MyClass的实例只能使用new运算符动态创建,从而确保MyClass的所有实例都是真正动态分配MyClass的重载新实例.我已经咨询了另一个关于如何实现这一点的线程,并得到了一些很好的答案: C++,防止在堆栈上创建类实例(在编译期间)

  2. 从我的类中的Vector成员恢复,并且只有指向Vector作为成员的指针,我将分别使用_aligned_malloc和分配_aligned_freector和dtor 来分配和解除分配.这个方法似乎很粗糙,容易出错,因为我不是编写这些类的唯一程序员(MyClass派生自Base类,其中许多类使用SSE).

但是,由于我的团队对这两种解决方案都不满意,因此我会向您寻求不同解决方案的建议.

aca*_*bot 4

如果您反对堆分配,另一个想法是在堆栈上过度分配并手动对齐(在此 SO post中讨论了手动对齐)。这个想法是分配字节数据 ( unsigned char) ,其大小保证包含必要大小的对齐区域 ( +15),然后通过从最移位区域 ( x+15 - (x+15) % 16、 或x+15 & ~0x0F) 向下舍入来找到对齐位置。我在键盘上发布了此方法的矢量操作示例(适用于g++ -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
         ) ) 
      {}
   ...
};
...
Run Code Online (Sandbox Code Playgroud)

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

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

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