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

Sta*_*ked 19 c++

我最近学到了一些关于对齐的知识,但我不确定在哪种情况下它会成为一个问题.我怀疑有两种情况:

第一个是使用数组时:

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?
}
Run Code Online (Sandbox Code Playgroud)

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

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);
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

更新

我正在使用64位Intel i7处理器和Darwin GCC.但我也将Linux,Windows(VC2008)用于32位和64位系统.

更新2

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

Jer*_*fin 14

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

[编辑:我本来应该更明确:填充允许在这里,在之后的结构data件(而不是之前它).

Foo array[4]; // total memory is 3 * 4 = 12 bytes. 
Run Code Online (Sandbox Code Playgroud)

这里不允许填充.数组必须是连续的.[编辑:但是数组中的结构之间不允许填充 - 数组中的一个struct必须紧跟在另一个之后 - 但如上所述,每个结构本身都可以包含填充.

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

再次,完全没问题 - 编译器必须允许这个1.

对于你的记忆库,预后并不是那么好.您已经分配了一个数组char,该数组必须足够对齐才能被访问char,但是不能保证以任何其他类型访问它.但是,char在任何情况下,都不允许实现对访问数据施加任何对齐限制.

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

或者,您可以动态分配块 - mallocoperator ::new保证任何内存块都对齐以用作任何类型.

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

例如,假设每种类型都需要"自然"对齐 - 即,对齐到与其自身大小相等的边界.可以在任何地址分配字符.我们假设short是2个字节,需要一个偶数地址,int和long是4个字节,需要4个字节的对齐.

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

char *a = Foo.Allocate<char>();
long *b = Foo.Allocate<long>();
Run Code Online (Sandbox Code Playgroud)

我们开始的块必须对齐任何类型,所以它绝对是一个偶数地址.当我们分配时char,我们只使用一个字节,因此下一个可用地址是奇数.然后我们为a分配足够的空间long,但它位于奇数地址,因此尝试取消引用它会产生UB.


1大多数情况下 - 最终,编译器可以在超出实现限制的幌子下拒绝任何事情.我很惊讶地发现真正的编译器有这个问题.

  • "这里不允许填充.数组必须是连续的.是的,但是`Foo`可以填充. (2认同)