我什么时候应该担心对齐问题?

2024-04-07

我最近了解了一些关于对齐的知识,但我不确定在哪些情况下它会成为问题。有两种情况我想知道:

第一个是使用数组时:

struct Foo {
    char data[3]; // size is 3, my arch is 64-bit (8 bytes)
};

Foo array[4]; // total memory is 3 * 4 = 12 bytes. 
              // will this be padded to 16?

void testArray() {
    Foo foo1 = array[0];
    Foo foo2 = array[1]; // is foo2 pointing to a non-aligned location?
                           // should one expect issues here?
}

第二种情况是使用内存池时:

struct Pool {
    Pool(std::size_t size = 256) : data(size), used(0), freed(0) { }

    template<class T>
    T * allocate() {
        T * result = reinterpret_cast<T*>(&data[used]);
        used += sizeof(T);
        return result;
    }

    template<class T>
    void deallocate(T * ptr) {
        freed += sizeof(T);
        if (freed == used) {
            used = freed = 0;
        }
    }

    std::vector<char> data;
    std::size_t used;
    std::size_t freed;
};

void testPool() {
    Pool pool;
    Foo * foo1 = pool.allocate<Foo>(); // points to data[0]
    Foo * foo2 = pool.allocate<Foo>(); // points to data[3],
                                       // alignment issue here?
    pool.deallocate(foo2);
    pool.deallocate(foo1);
}

我的问题是:

  • 两个代码示例是否存在对齐问题?
  • 如果是,那么如何修复它们?
  • 我可以在哪里了解更多相关信息?

Update

我使用的是带有 Darwin GCC 的 64 位 Intel i7 处理器。 但我也使用Linux、Windows(VC2008)32位和64位系统。

Update 2

池现在使用向量而不是数组。


struct Foo {
    char data[3]; // size is 3, my arch is 64-bit (8 bytes)
};

Padding is允许在此处,在结构体之后data成员——但不在它之前,也不在以下元素之间data.

Foo array[4]; // total memory is 3 * 4 = 12 bytes. 

此处数组中的元素之间不允许填充。数组要求是连续的。但是,如上所述,在 a 内部允许填充Foo,遵循其data成员。所以,sizeof(someFoo.data)必须是 3,但是sizeof(someFoo)可能是(通常是 4)。

void testArray() {
    Foo * foo1 = array[0];
    Foo * foo2 = array[1]; // is foo2 pointing to a non-aligned location?
                           // should I expect issues here?
}

Again, perfectly fine -- the compiler must allow this1.

但对于你的内存池来说,预测却没有那么好。你已经分配了一个数组char,它必须充分对齐才能被访问为char,但像任何其他类型一样访问它是not保证工作。不允许该实现对访问数据施加任何对齐限制,如下所示char无论如何。

通常,对于这种情况,您可以创建您关心的所有类型的联合,并分配一个数组。这保证了数据对齐以用作联合中任何类型的对象。

或者,您可以动态分配块——两者malloc and operator ::new保证任何内存块都对齐以用作任何类型。

编辑:更改要使用的池vector<char>情况有所改善,但只是轻微改善。这意味着first您分配的对象将起作用,因为向量持有的内存块将被(间接)分配operator ::new(因为您没有另外指定)。不幸的是,这并没有多大帮助——第二次分配可能完全错位。

例如,假设每种类型都需要“自然”对齐,即对齐到等于其自身大小的边界。字符可以分配在任何地址。我们假设 Short 是 2 个字节,需要偶数地址,而 int 和 long 是 4 个字节,需要 4 字节对齐。

在这种情况下,请考虑如果您这样做会发生什么:

char *a = Foo.Allocate<char>();
long *b = Foo.Allocate<long>();

我们开始的块必须针对任何类型进行对齐,因此它绝对是偶数地址。当我们分配char,我们只用了一个字节,因此下一个可用地址是奇数。然后我们分配足够的空间long,但它位于一个奇怪的地址,因此尝试取消引用它会给出 UB.


1 Mostly anyway -- ultimately, a compiler can reject just about anything under the guise of an implementation limit having been exceeded. I'd be surprised to see a real compiler have a problem with this though.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

我什么时候应该担心对齐问题? 的相关文章

随机推荐