【问题标题】:Allocating an array of aligned struct分配对齐结构的数组
【发布时间】:2015-06-19 15:16:25
【问题描述】:

我正在尝试分配一个结构数组,我希望每个结构都对齐到 64 个字节。

我试过这个(它现在只适用于 Windows),但它不起作用(我用 VS2012 和 VS2013 试过):

struct __declspec(align(64)) A
{
    std::vector<int> v;

    A()
    {
        assert(sizeof(A) == 64);
        assert((size_t)this % 64 == 0);
    }

    void* operator new[] (size_t size)
    {
        void* ptr = _aligned_malloc(size, 64); 
        assert((size_t)ptr % 64 == 0);
        return ptr;
    }

    void  operator delete[] (void* p)
    {
        _aligned_free(p);
    }
};

int main(int argc, char* argv[])
{
    A* arr = new A[200];
    return 0;
}

断言((size_t)this % 64 == 0) 中断(模返回16)。如果结构只包含简单类型,它看起来似乎有效,但是当它包含 std 容器(或其他一些 std 类)时会中断。

我做错了吗?有没有办法正确地做到这一点? (最好兼容 c++03,但任何适用于 VS2012 的解决方案都可以)。

编辑: 正如 Shokwav 所暗示的,这很有效:

A* arr = (A*)new std::aligned_storage<sizeof(A), 64>::type[200];
// this works too actually:
//A* arr = (A*)_aligned_malloc(sizeof(A) * 200, 64);
for (int i=0; i<200; ++i)
    new (&arr[i]) A();

所以看起来它与 new[] 的使用有关......我很好奇是否有人有解释。

【问题讨论】:

  • 在您进行编辑之后,您实际上是在调用“放置”新运算符,它确实有效(显然)。但是,这可能会泄漏,因为没有代码会为数组中的每个项目调用 A 的析构函数,即使您调用“free(arr)”或“delete arr”
  • 没关系,我会手动调用析构函数:arr[i].~A()

标签: c++ memory-alignment


【解决方案1】:

我想知道为什么你需要这么大的对齐要求,而且在结构中存储一个动态堆分配对象。但你可以这样做:

struct __declspec(align(64)) A
{
    unsigned char ___padding[64 - sizeof(std::vector<int>)];
    std::vector<int> v;

    void* operator new[] (size_t size)
    {
        // Make sure the buffer will fit even in the worst case
        unsigned char* ptr = (unsigned char*)malloc(size + 63);

        // Find out the next aligned position in the buffer
        unsigned char* endptr = (unsigned char*)(((intptr_t)ptr + 63) & ~63ULL);
        // Also store the misalignment in the first padding of the structure 
        unsigned char misalign = (unsigned char)(endptr - ptr);
        *endptr = misalign;
        return endptr;
    }

    void  operator delete[] (void* p)
    {
        unsigned char * ptr = (unsigned char*)p;
        // It's required to call back with the original pointer, so subtract the misalignment offset
        ptr -= *ptr;
        free(ptr);
    }
};

int main()
{
    A * a = new A[2];
    printf("%p - %p = %d\n", &a[1], &a[0], int((char*)&a[1] - (char*)&a[0]));
    return 0;
}

我没有你的 align_malloc 和 free 函数,所以我提供的实现是这样做的:

  1. 它分配更大以确保它适合 64 字节边界
  2. 它计算从分配到最近的 64 字节边界的偏移量
  3. 它将“偏移量”存储在第一个结构的填充中(否则我每次都需要更大的分配空间)
  4. 这用于计算返回到 free() 的原始指针

输出:

0x7fff57b1ca40 - 0x7fff57b1ca00 = 64

警告:如果您的结构中没有填充,那么上面的方案将损坏数据,因为我会将未对齐偏移量存储在将被构造函数覆盖的位置内部成员。 请记住,当您执行“new X[n]”时,“n”必须存储在“某处”,因此在调用 delete[] 时,将完成对析构函数的“n”调用。通常,它存储在返回的内存缓冲区之前(new 可能会分配所需的大小 + 4 来存储元素的数量)。这里的方案避免了这种情况。

另一个警告:由于 C++ 调用此运算符时会在存储数组元素数量的大小中包含一些额外的填充,因此您可能仍会在返回的指针地址中获得“移位”你的对象。你可能需要考虑它。这就是 std::align 所做的,它占用额外的空间,像我一样计算对齐并返回对齐的指针。但是,您不能在 new[] 重载中同时完成这两项工作,因为“计数存储”会发生在从 new() 返回之后。但是,您可以通过一次分配计算一次“计数存储”空间,并在 new[] 实现中相应地调整偏移量。

【讨论】:

  • 如果你没有实现标准库,你不应该使用多个下划线;)
  • 如果我有 2 个下划线,你是对的,但我有 3 个(在这种情况下我不在文件范围内)。无论如何,您可以使用任何您想要的填充名称。
  • 我实际上不需要在结构中存储 std::vector ,这只是一个例子。在这种情况下,64 字节对齐也是一个示例(尽管在缓存线为 64 字节的处理器上执行多线程操作时,这仍然有助于避免错误共享)。
  • 问题不在于元素之间的填充,而在于元素本身的对齐方式。
  • 其实没有前者就不能得到后者。请参阅我的编辑以了解如何处理它。
猜你喜欢
  • 2021-12-29
  • 2021-07-11
  • 1970-01-01
  • 2013-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
  • 1970-01-01
相关资源
最近更新 更多