我何时应该担心对齐?

| 我最近已经学到了一些有关对齐的知识,但是我不确定在哪种情况下会出现问题。我想知道两种情况: 第一个是使用数组时:
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);
}
我的问题是: 两个代码示例中是否存在任何对齐问题? 如果是,那么如何解决? 在哪里可以了解更多信息? 更新资料 我在达尔文GCC上使用64位Intel i7处理器。 但是我也将Linux,Windows(VC2008)用于32位和64位系统。 更新2 池现在使用向量而不是数组。     
已邀请:
        
struct Foo {
    char data[3]; // size is 3, my arch is 64-bit (8 bytes)
};
[编辑:我应该更明确:在此允许填充,在
data
成员之后的结构中(但不允许在其之前)]。
Foo array[4]; // total memory is 3 * 4 = 12 bytes. 
此处不允许填充。数组必须是连续的。 [编辑:但是数组中的结构之间不允许填充-数组中的一个
struct
必须紧跟在另一个之后-但如上所述,每个结构本身都可以包含填充。]
void testArray() {
    Foo * foo1 = array[0];
    Foo * foo2 = array[1]; // is foo2 pointing to a non-aligned location?
                           // should I expect issues here?
}
同样,非常好-编译器必须允许this1。 对于您的内存池,预后并不好。您已分配了一个数组
char
,它必须足够对齐才能作为
char
进行访问,但是不能保证像其他任何类型一样对其进行访问。但是无论如何,该实现都不允许对访问数据施加任何对齐限制,例如“ 7”。 通常,在这种情况下,您将创建一个您关心的所有类型的并集,并为其分配一个数组。这样可以确保将数据对齐以用作联合中任何类型的对象。 另外,您可以动态分配您的块-
malloc
operator ::new
保证任何内存块都可以对齐以用作任何类型。 编辑:将池更改为使用ѭ12会改善这种情况,但效果稍有改善。这意味着您分配的第一个对象将起作用,因为向量持有的内存块将被分配(间接)为
operator ::new
(因为您没有另外指定)。不幸的是,这并没有多大帮助-第二分配可能会完全错位。 例如,假设每种类型都需要“自然”对齐-即对齐等于其自身大小的边界。可以在任何地址分配一个字符。我们假设short是2个字节,并且需要一个偶数地址,int和long是4个字节,并且需要4字节对齐。 在这种情况下,请考虑以下情况会发生什么:
char *a = Foo.Allocate<char>();
long *b = Foo.Allocate<long>();
我们开始使用的块必须针对任何类型进行对齐,因此绝对是一个偶数地址。当我们分配“ 7”时,我们仅用完一个字节,因此下一个可用地址为奇数。然后,我们为ѭ16分配足够的空间,但是它位于一个奇数地址,因此尝试取消引用它会得到UB。 1无论如何-最终,编译器可以以超出实现限制的名义拒绝几乎任何东西。看到真正的编译器对此有问题,我会感到惊讶。     
        还没有人提到内存池。这具有巨大的对准问题。
T * result = reinterpret_cast<T*>(&data[used]);
那不好当您接管内存管理时,您需要接管内存管理的所有方面,而不仅仅是分配。尽管您可能分配了适当的内存量,但您根本没有解决对齐问题。 假设您使用
new
malloc
分配一个字节。打印它的地址。再次执行此操作,并打印此新地址:
char * addr1 = new char;
std::cout << \"Address #1 = \" << (void*) addr1 << \"\\n\";
char * addr2 = new char;
std::cout << \"Address #2 = \" << (void*) addr2 << \"\\n\";
在Mac等64位计算机上,您会看到两个打印地址都以零结尾,并且通常相隔16个字节。您尚未在此处分配两个字节。您已分配32!那是因为ѭ10总是返回一个对齐的指针,这样它可以用于任何数据类型。 以十六进制打印时,在不以8或0结尾的地址上放置一个double或long long int,这样很可能会导致核心转储。 Double和long long int必须与8个字节边界对齐。类似的约束适用于普通的旧香草整数(int32_t);这些需要在4个字节的边界上对齐。您的内存池没有这样做。     
通常,对于大多数数据结构而言,不必担心预先对齐。编译器通常会做正确的事情。对于未对齐的数据,出汗时间惩罚的日子至少比我们落后了20年。 剩下的唯一问题是非法的未对齐数据访问,这种访问仅发生在少数CPU架构上。编写代码,使之有意义。测试一下。如果发生未对齐的数据异常,那么该是找出解决方法的时候了。通过添加命令行选项,可以轻松解决大多数情况。有一些需要更改结构:重新排序元素,或显式插入未使用的填充元素。     
        对齐方式由编译器透明地处理-sizeof和数组访问始终会考虑任何对齐方式,您不必在意。 内存池示例中有一个错误-如果调用deallocate(),它将始终取消分配最后分配的指针,而不是给定的指针。     

要回复问题请先登录注册