C++ 防止堆上未对齐的数据

C++ 防止堆上未对齐的数据,c++,visual-c++,alignment,C++,Visual C++,Alignment,我正在构建一个使用SSE intrinsics函数的类层次结构,因此类的一些成员需要16字节对齐。对于堆栈实例,我可以使用\uu declspec(align(#)),如下所示: typedef __declspec(align(16)) float Vector[4]; class MyClass{ ... private: Vector v; }; 现在,由于\uuu declspec(align(#))是一个编译指令,以下代码可能会导致堆上的Vector实例未对齐: MyClass *m

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

typedef __declspec(align(16)) float Vector[4];
class MyClass{
...
private:
Vector v;
};
现在,由于
\uuu declspec(align(#))
是一个编译指令,以下代码可能会导致堆上的Vector实例未对齐:

MyClass *myclass = new MyClass;
我也知道,通过重载new和delete操作符,使用
\u aligned\u malloc
\u aligned\u 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);
}
...
由于
myclass
的myclass实例是在NotMyClass的动态实例上静态创建的,因此由于Vector的
\u declspec(align(16))
指令,myclass将与nmc的开头相对对齐16字节。但这是毫无价值的,因为nmc是使用NotMyClass的新运算符在堆上动态分配的,这不一定能确保(而且很可能不能)16字节对齐

到目前为止,我只能想到两种方法来解决这个问题:

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

    class NotMyClass{ //Not my code, which I have little or no influence over
    ...
    MyClass myclass;
    ...
    };
    int main(){
        ...
        NotMyClass *nmc = new NotMyClass;
        ...
    }
    
    MyClass myclass;
    
    这意味着,只能使用new操作符动态创建MyClass的实例,从而确保MyClass的所有实例都使用MyClass的重载new进行真正的动态分配。我已经就如何实现这一点咨询了另一条线索,并得到了一些很好的答案:

  • 恢复在我的类中使用Vector成员,并且只使用指向Vector的指针作为成员,我将分别使用ctor和dtor中的
    \u aligned\u malloc
    \u aligned\u free
    分配和取消分配这些指针。这种方法看起来粗糙而且容易出错,因为我不是编写这些类的唯一程序员(MyClass派生自基类,其中许多类使用SSE)


  • 但是,由于这两种解决方案在我的团队中都不受欢迎,所以我来向您征求另一种解决方案的建议。

    如果您反对堆分配,另一个想法是在堆栈上过度分配并手动对齐(在中讨论了手动对齐)。其思想是分配字节数据(
    unsigned char
    ),其大小保证包含必要大小的对齐区域(
    +15
    ),然后通过从移位最多的区域向下取整(
    x+15-(x+15)%16
    ,或
    x+15&~0x0F
    )来找到对齐位置。我在(for
    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
             ) ) 
          {}
       ...
    };
    ...
    
    class-MyClass{
    ...
    无符号字符dPtr[sizeof(float)*4+15];//过度分配的数据
    float*vPtr;//要对齐的float ptr
    公众:
    MyClass(无效):
    vPtr(重新解释铸件(
    (重新解释铸件(dPtr)+15)和~0x0F
    ) ) 
    {}
    ...
    };
    ...
    
    构造函数确保vPtr对齐(注意类声明中成员的顺序很重要)

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

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

    您可以使用“placement new”


    当然,在销毁对象时需要小心,方法是调用析构函数,然后释放空间。你不能只调用delete。您可以使用具有不同功能的shared_ptr来自动处理该问题;这取决于处理共享\u ptr(或指针的其他包装器)的开销是否是您的问题。

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

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

    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
    
    最后,您需要一些强制转换来操作它(到目前为止,它是原始内存):

    float*MyClass::AccessVector()
    {
    返回重新解释(void*)和mVector);
    }
    
    使用未对齐的SSE加载/存储指令不是选项吗?(如果计算不受加载/存储性能影响的影响应该很小。)如果您的团队使用调试版本,您可以尝试添加断言以强制执行健全^Walignment。@inflagranti:不太可能。首先,在一些(公认罕见的情况下)装载/存储操作是我们最关心的问题。但这些通常是在具有大数据规模的动态分配向量上执行的。这些成员通常是传递给类的单个参数。程序员必须在一个128字节的向量上执行一个简单的SET函数。SSE set函数仅适用于对齐的数据。不,我们不能把这些向量当作静态向量members@ninjalj:你能扩展一下吗?您是否声称此问题只会在调试模式下发生?(我承认,我没有在调试之外测试过它)断言如何帮助修复它?(不只是知道实例未对齐)我的意思是,您可以尝试在调试构建中捕获违规者,并使用bat或其他contundent对象将其打通。这对我不起作用,因为NotMyClass实例是在我的代码范围之外创建的,并且很难强制程序员使用新的布局,就像我不想强制NotMyClass重载它是新的运算符那样,我不理解你的问题。你似乎在问如何影响那些不受你控制的程序员。如果是这样的话,最好的答案可能是“善意地要求他们做正确的事情”
    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));
    }