更改派生类型的对齐

Changing alignment in derived type

本文关键字:对齐 类型 派生      更新时间:2023-10-16

说您有这两种类型:

struct alignas(8) Base { double a; double b; }
struct alignas(16) Derived : public Base {}

和这些功能:

void Foo(Base b)
void Bar(Base* start, Base* end)

打电话

是合法的
Foo(someDerived)
Bar(someDerivedArray, someDerivedArray + arrayLength)

在所有情况下?这是否取决于Base的内容和特定的对齐限制?我绝对可以看到,如果derived是32位对齐的,因为someDerivedArrayBar的元素之间会有一个间隙,但我不确定。

xy说明:我们使用的是GPGPU库推力,其复杂类型在设备代码和主机代码之间的比对不一致;我们正在尝试弄清楚如何解决这个问题。一种选择是定义我们自己的复杂类型,该类型从thrust::complex衍生而来,但使用相同的对齐方式并使用它,但是当然,各种推力功能都会期望thrust::complex,因此我们将切入或施放到较少分配的类型中。

Base*上执行指针算术,如果指针实际上指向Derived对象,则总是UB。对齐是无关紧要的。如果 BaseDerived具有相同的 size ,那么这似乎是有效的,但是它仍然是ub。

将指针传递到Derived对象到占用Base*的函数,并且执行指针算术通常很好,请注意,如果该函数将在指针上执行delete,则那么Base必须具有虚拟驱动器。同样,对齐不相关。

增加Derived对齐的原因是没有区别的,即Base子对象仍然被迫根据Base的对齐方式对齐。(这就是为什么一般而言,您可以增加相对于基类的派生类的对齐,但不能减小。)因此,当Derived*转换为Base*时,您仍然会得到有效的指针值:它指向一个Base对象和该对象正确对齐。(实际上,没有一个对象没有适当对齐的对象。任何创建此类对象的尝试都会引起UB。)

因此,只要Foo不尝试delete指针,Foo(someDerived)通常就可以了,而Bar(someDerivedArray, someDerivedArray + arrayLength)则不会。

alignas仅适用于基础和派生;他们不适用于其成员:ab

所以没有问题。

#include <iostream>
using namespace std;
struct A
{
    int x, y, z, w;
};
struct alignas(64) B
{
    alignas(64) int x;
    alignas(64) float y, z, w;
};
struct alignas(64) C : public A
{
};
int main()
{
A a;
B b;
C c;
cout << size_t((char*)(void*)&(a.y)-(char*)(void*)&(a.x)) << endl;
cout << size_t((char*)(void*)&(b.y)-(char*)(void*)&(b.x)) << endl;
cout << size_t((char*)(void*)&(c.y)-(char*)(void*)&(c.x)) << endl;  
return 0;
}

它返回

4
64
4

编辑:如果您不相信我,请自己运行代码。我不确定为什么有人会认为alignas会与班级的每个成员强制对齐。alignas的定义指出,它适用于课程本身(只有类的对齐受其成员的对齐的影响,而不是受班级影响的成员)。

编辑2 :关于推力 - 如果设备和主机决定类中的不同对齐方式,那么这是需要同步的不同编译器一致性的错误。您可以将错误提交给Nvidia,因为这是他们的问题。

如何解决此类问题?这是一个复杂的问题...祝你好运。