指定 64 位对齐

Posted

技术标签:

【中文标题】指定 64 位对齐【英文标题】:Specifying 64-bit alignment 【发布时间】:2012-06-12 15:25:07 【问题描述】:

给定一个结构定义,如

struct foo 
    int a, b, c;
;

什么是最好的(最简单、最可靠和可移植的)方法来指定它应该始终与 64 位地址对齐,即使在 32 位版本上也是如此?我正在使用 C++11 和 GCC 4.5.2,并希望也支持 Clang。

【问题讨论】:

我很好奇;为什么 32 位系统上的对齐方式很重要?或者,实际上,在 64 位系统上,因为该结构通常不需要超过 32 位对齐。 一旦编译器支持它,你就可以使用 alignas。 @JonathanLefler:我假设允许某些自动 sse 优化。 gcc 最近刚刚添加了一些 __builtin_assume_aligned 来告诉编译器这些东西应该是对齐的。使用gcc.godbolt.org 示例可能会提供更多见解。 @PlasmaHH:是的,但 GCC 4.5.2(甚至 4.7.0)没有。而且您必须将 64 位对齐类型传递给 alignas。那么你会通过哪种类型呢? 因为我打算使用指针的低位作为标记位。典型的用例是 64 位平台和重指针数据结构,给我三个标记位,但我想确保代码在 32 位编译时仍然有效。 【参考方案1】:

[[gnu::aligned(64)]] in c++11 annotation std::atomic <int64_t> ob [[gnu::aligned(64)]]

【讨论】:

【参考方案2】:

您应该使用__attribute__((aligned(8))。但是,我发现此描述仅确保分配的结构大小是 8 字节的倍数。它不能确保起始地址是倍数。

例如。我使用__attribute__((aligned(64)),malloc 可能会返回一个 64Byte 长度的结构,其起始地址为 0xed2030。

如果你想对齐起始地址,你应该使用aligned_alloc: gcc aligned allocation。 aligned_alloc(64, sizeof(foo) 将返回 0xed2040。

【讨论】:

【参考方案3】:

便携?我真的不知道真正便携的方式。 GCC 有__attribute__((aligned(8))),其他编译器也可能有等价物,您可以使用预处理器指令检测。

【讨论】:

【参考方案4】:

我很确定 gcc 4.5.2 已经足够老了,它还不支持标准版本,但是 C++11 添加了一些专门用于处理对齐的类型 -- std::aligned_storagestd::aligned_union 等等东西(有关详细信息,请参阅第 20.9.7.6 节)。

在我看来,最明显的方法是使用 Boost 的 aligned_storage 实现(或 TR1,如果你有的话)。如果您不希望这样,我仍然会认真考虑在您的大多数代码中使用标准版本,并且只需编写一个小的实现供您自己使用,直到您更新到实现该标准的编译器。然而,可移植代码看起来仍然与大多数直接使用 __declspec(align...__attribute__(__aligned__, ... 之类的代码略有不同。

特别是,它只是给你一个请求大小的原始缓冲区和请求的对齐。然后由您决定使用placement new之类的东西在该存储中创建您类型的对象。

对于它的价值,这里是基于 gcc 的 __attribute__(__aligned__,... 指令的 aligned_storage 实现的快速测试:

template <std::size_t Len, std::size_t Alignment>
struct aligned_storage 
    typedef struct 
        __attribute__(__aligned__(Alignment)) unsigned char __data[Len];
     type;
;

一个快速测试程序来展示如何使用它:

struct foo 
    int a, b, c;

    void *operator new(size_t, void *in)  return in; 
;

int main() 
    stdx::aligned_storage<sizeof(foo), 8>::type buf;

    foo& f = *new (static_cast<void*>(&buf)) foo();

    int address = *reinterpret_cast<int *>(&f);

    if (address & 0x3 != 0)
        std::cout << "Failed.\n";

    f.~foo();

    return 0;

当然,在实际使用中,您会封装/隐藏我在这里展示的大部分丑陋。如果你这样放着,(理论/未来)可移植性的代价可能过高。

【讨论】:

【参考方案5】:

以下是合理可移植的,因为它适用于许多不同的实现,但不是全部:

union foo 
    struct int a, b, c;  data;
    double padding1;
    long long padding2;
;

static char assert_foo_size[sizeof(foo) % 8 == 0 ? 1 : -1];

这将无法编译,除非:

编译器在foo 中添加了一些填充,以使其达到 8 的倍数,这通常只会在对齐要求的情况下发生,或者 foo.data的布局非常奇怪,或者 long longdouble 之一大于 3 个整数,并且是 8 的倍数。这并不一定意味着它是 8 对齐的。

鉴于您只需要支持 2 个编译器,并且 clang 在设计上与 gcc 相当兼容,因此只需使用有效的 __attribute__。如果您现在想编写可以(希望)在您未测试的编译器上工作的代码,请只考虑做其他事情。

C++11 增加了alignof,可以测试而不是测试大小。它将消除误报,但仍然会给您留下一些一致的实现,在这些实现上,联合无法创建您想要的对齐,因此无法编译。此外,我的 sizeof 技巧非常有限,如果您的结构有 4 个整数而不是只有 3 个整数,它根本没有帮助,而 alignof 的相同之处。我不知道什么版本的gcc和clang支持alignof,所以我一开始没有用它。没想到很难做到。

顺便说一句,如果foo 的实例是动态分配的,那么事情会变得更容易。首先,我怀疑 glibc 或类似的 malloc 实现无论如何都会 8 对齐——如果有一个基本类型具有 8 字节对齐,那么 malloc 必须这样做,我认为 glibc malloc 总是这样做,而不是担心在任何给定平台上是否存在。其次,可以肯定的是posix_memalign

【讨论】:

+1 非常好(没有任何讨厌的编译器扩展)。甚至可以将 data 结构设为匿名,以获得类似于 OP 的原始结构的行为。 针对已定义的平台/编译器集的良好解决方案。如果真正的可移植性是您的目标,那么序列化数据的二进制兼容性可能不应该是额外的目标。 为什么加倍/长长??? uint64_t 可以更安全地使用,此外,可以使用位字段隐藏填充:uint64_t : 0; 我认为你不能在 32 位架构上以这种方式保证 64 位对齐... @Aconcagua:确实。在没有 8 位对齐 doublelong long 的 32 位架构上,代码将无法编译。失败通知你需要做一些不可移植的事情。不幸的是,提问者的“最简单、最可靠、最便携”的要求有点像铁三角。【参考方案6】:

既然你说你正在使用 GCC 并希望支持 Clang,那么 GCC 的 aligned attribute 应该可以解决问题:

struct foo 
    int a, b, c;
 __attribute__((__aligned__(8))); // aligned to 8-byte (64-bit) boundary

【讨论】:

这是不可移植的。但是,什么都不会发生。 @JohnDibling:我知道。它可以移植到有问题的两个编译器。 根据您链接的文档,这不应该是 __attribute__((aligned (8))) 吗? @D0SBoots:第二段:“您还可以在其关键字前后使用 `__' 指定这些属性中的任何一个。这使您可以在头文件中使用这些属性而无需担心关于可能的同名宏。例如,您可以使用__aligned__ 而不是aligned。" 小心!您的系统中可能存在最大对齐。遗憾的是,它可能是在 linker 中实现的。因此,您不会收到编译时错误,并且可能大型对齐请求可以在更细粒度的边界上静默对齐。这里是龙。

以上是关于指定 64 位对齐的主要内容,如果未能解决你的问题,请参考以下文章

结构体对齐——结构体内存布局

aarch64 上未对齐 SIMD 加载/存储的性能

在未对齐的字节边界上有效地打包 10 位数据

Struct 和 Union区别

结构体嵌套对齐

内存对齐:C/C++编程中的重要性和技巧