C++ 防止堆上未对齐的数据
我正在构建一个使用SSE intrinsics函数的类层次结构,因此类的一些成员需要16字节对齐。对于堆栈实例,我可以使用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
\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字节对齐
到目前为止,我只能想到两种方法来解决这个问题:
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进行真正的动态分配。我已经就如何实现这一点咨询了另一条线索,并得到了一些很好的答案:
\u aligned\u malloc
和\u aligned\u free
分配和取消分配这些指针。这种方法看起来粗糙而且容易出错,因为我不是编写这些类的唯一程序员(MyClass派生自基类,其中许多类使用SSE)但是,由于这两种解决方案在我的团队中都不受欢迎,所以我来向您征求另一种解决方案的建议。如果您反对堆分配,另一个想法是在堆栈上过度分配并手动对齐(在中讨论了手动对齐)。其思想是分配字节数据(
unsigned char
),其大小保证包含必要大小的对齐区域(+15
),然后通过从移位最多的区域向下取整(x+15-(x+15)%16
,或x+15&~0x0F
)来找到对齐位置。我在(forg++-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对齐(注意类声明中成员的顺序很重要)
这种方法是有效的(包含类的堆/堆栈分配与对齐无关),是可移植的(我认为大多数编译器提供指针大小的uintuintptpr\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));
}