指定 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_storage
和 std::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 long
和 double
之一大于 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 位对齐 double
或 long 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 位对齐的主要内容,如果未能解决你的问题,请参考以下文章