内存对齐:如何使用alignof/alignas?

113

我现在使用共享内存。

我不理解 alignofalignas

cppreference 没有清晰地说明:alignof 返回 "alignment",但是 "alignment" 是什么呢?是为了对齐下一个块而需要添加的字节数吗?还是填充后的大小?Stack overflow/博客条目也没有清晰地解释。

有人能清楚地解释一下 alignofalignas 吗?


4
cppreference旨在成为参考而非教程。 - Cubbi
1
@Cubbi:你也可以在cplusplus.com上查看,有争议哪个网站更好,对于某些主题,cplusplus更好,对于其他cppreference更好,我发现两个网站有时都不够。 - CoffeDeveloper
2
@DarioOO 我只是在回答为什么cppreference页面上的alignof没有解释对齐概念(现在正在进行中的对象页面上已经有了)。我不明白cplusplus.com的相关性。 - Cubbi
所有的答案都谈到了性能,但是有些平台的硬件无法加载未对齐的int - Caleth
5个回答

124

对于一个数值,其第一个字节能够存储在哪些内存位置上是有限制的,这个限制被称为“对齐”。(对齐操作是为了提高处理器性能并且允许使用某些仅适用于特定对齐数据的指令,例如SSE需要对齐到16字节,而AVX则需要对齐到32字节。)

对齐值为16意味着内存地址必须是16的倍数,才会被视作有效地址。

alignas

强制对齐到所需的字节数。您只能对齐到2的幂次方:1、2、4、8、16、32、64、128等。

#include <cstdlib>
#include <iostream>

int main() {
    alignas(16) int a[4];
    alignas(1024) int b[4];
    printf("%p\n", a);
    printf("%p", b);
}

示例输出:

0xbfa493e0
0xbfa49000  // note how many more "zeros" now.
// binary equivalent
1011 1111 1010 0100 1001 0011 1110 0000
1011 1111 1010 0100 1001 0000 0000 0000 // every zero is just a extra power of 2

另一个关键词

alignof

非常方便,你无法做类似的事情

int a[4];
assert(a % 16 == 0); // check if alignment is to 16 bytes: WRONG compiler error

但是你可以做

assert(alignof(a) == 16);
assert(alignof(b) == 1024);

请注意,实际上这比简单的“%”(模运算)操作更严格。事实上,我们知道,对齐于1024字节的内容必定是对齐于1、2、4、8个字节的。

 assert(alignof(b) == 32); // fail.

更加准确地说,“alignof”返回某个对象对齐所需的最大2的幂次。

此外,alignof是一种很好的方式来事先了解基本数据类型的最小对齐要求(对于字符可能返回1,对于浮点数可能返回4等)。

仍然合法:

alignas(alignof(float)) float SqDistance;

对齐值为16的内容将被放置在下一个可用地址,该地址是16的倍数(最后使用地址可能会有隐式填充)。


16
sizeof 不同,alignof 只能应用于 type-id - neverhoodboy
1
@Serthy 为了澄清,alignof 是一个编译时常量。而 alignas 不是,需要由您的实现支持 new(标准要求),或者通过自定义 std allocator 来支持。 - Aidiakapi
2
回答不错,但需要处理structstruct成员中的static。在像Clang这样的编译器下,alignas__attribute__((aligned))更加棘手。 - jww
1
“16字节对齐”意味着内存地址必须是16的倍数才是有效的地址。这来自哪里?根据C++标准,对齐是一个实现定义的整数值,表示给定对象可以分配在连续地址之间的字节数。 - Daniel Langr
1
CPU 架构指令集。 - CoffeDeveloper
显示剩余7条评论

17

对齐与填充不同(尽管有时会引入填充以满足对齐要求),它是C++类型的内在属性。用标准术语表达(3.11[basic.align]

对象类型具有对齐要求(3.9.1, 3.9.2),这些要求限制了该类型对象可以分配的地址。对齐是一个实现定义的整数值,表示给定对象可以分配的连续地址之间的字节数。每个对象类型对其每个对象都施加对齐要求;可以使用对齐说明符(7.6.2)请求更严格的对齐。


1
非常有趣。您介意给一些例子吗?alignof(struct X) == sizeof(struct X)吗?为什么不呢? - Offirmo
4
不,除非偶然情况下:在正常系统上,结构体X { char a; char b }的大小为2,对齐要求为1(因为char可以分配在任何地址),但它可以被分配到任何地址。 - Cubbi
对齐要求为1吗?我明白了:我以为对齐总是在“自然”的32位/64位边界上,但显然不是这样。这就解释了一些事情...因此,在通常的机器上,alignof()结果将始终最大为4(32位)或8(64位)。我是对的吗? - Offirmo
@Offirmo "自然"对齐将在alignof(std::max_align_t)达到最大值,这在我的Linux上是16(无论是否编译为-m32或-m64),但您可以使用alignas使其更严格。 - Cubbi

7
每种类型都有对齐要求。通常,这是为了能够高效地访问该类型的变量,而不必使CPU生成多个读/写访问以便到达任何给定成员。此外,它还确保整个变量的有效复制。alignof将返回给定类型的对齐要求。 alignas用于强制数据类型的对齐(只要它不比alignof所述数据类型的要求更严格)。

7

对齐是与内存地址有关的属性。简单地说,如果地址 X 对齐于 Z,那么 X 是 Z 的倍数,即 X = Zn+0。这里重要的是 Z 总是2的幂。

对齐是内存地址的属性,表示为数字地址模除2的幂。例如,地址0x0001103F模除4为3。该地址被认为对齐于4n+3,其中4表示所选的2的幂。地址的对齐取决于所选的2的幂。同样的地址模除8为7。如果一个地址的对齐是Xn + 0,那么该地址就被认为对齐于X。

上述语句可在Microsoft C++参考中找到。

如果数据项存储在内存中,其地址与其大小对齐,则该数据项被称为自然对齐,否则为未对齐。例如:如果用占用4个字节的整数变量存储在对齐于4的地址中,则可以说该变量是自然对齐的,即变量的地址应该是4的倍数。

编译器总是尽力避免未对齐。对于简单的数据类型,地址被选择为变量大小的字节数的倍数。在结构体的情况下,编译器还会适当地填充以实现自然对齐和访问。这里的结构体将对齐于结构体中不同数据项大小的最大值。例如:

    struct abc
   {
        int a;
        char b;
   };

这里,结构体abc被对齐到4,这是int成员的大小,显然大于1个字节(char成员的大小)。

alignas

这个限定符用于将用户定义的类型(如结构体、类等)对齐到一个2的幂次方数值。

alignof

这种运算符用于获取结构体或类类型对齐的值。例如:

#include <iostream>
struct alignas(16) Bar
{
    int i; // 4 bytes
    int n; // 4 bytes
    short s; // 2 bytes
};
int main()
{
    std::cout << alignof(Bar) << std::endl; // output: 16
}

6
要理解 alignasalignof,你必须了解什么是数据对齐。

这里有一篇好的指南:https://developer.ibm.com/articles/pa-dalign//

数据对齐(简介)

解释1

数据对齐 是指将数据存储到地址等于某个字长倍数的内存中。

解释2

对齐 是指内存地址的属性,采用数值地址模一个2的幂次方来表示。例如,地址0x0001103F模4的结果为3。该地址被称为对齐于4n+3,其中4表示所选幂次方。一个地址的对齐方式取决于所选择的幂次方。同样一个地址模8的结果为7。如果地址的对齐方式为Xn+0,则称其对齐于X。

CPU执行操作存储在内存中的数据的指令。数据由它们在内存中的地址标识。单个数据还有一个大小。如果一个数据对其大小对齐,那么我们就称其自然对齐。否则称为未对齐。例如,如果用于标识8字节浮点数据的地址具有8字节对齐,则该数据自然对齐。

好了,你已经理解了“数据对齐”这一概念 恭喜你!

alignas 是什么意思

解释

alignas (N) 指定在 N 的倍数地址中放置数据。

N - 模2的幂次方的数字

语法:

alignas( the numeric address modulo a power of 2 )
alignas( alignof(type-id) )
alignas( type-id )
alignas 说明符可用于以下情况:
  • 声明或定义 class / struct / unionenumeration;

  • 非位域类数据成员的声明;

  • 变量的声明,但不能应用于以下内容:

    • 函数参数;
    • catch 子句的异常参数。

示例:

struct alignas(256) name1 // every object of type name1 will be aligned to 256-byte boundary
{
    float test[4];
};

alignas(128) char name2[128]; // the array "name2" will be aligned to 128-byte boundary

补充1

alignas类型限定符是一种可移植的、符合C++标准的方法,用于指定变量和用户定义类型的自定义对齐方式。

补充2

#include <iostream>

struct alignas(16) Bar
{
    int i;       // 4 bytes
    int n;      // 4 bytes
    alignas(4) char arr[3];
    short s;          // 2 bytes
};

int main()
{
    std::cout << alignof(Bar) << std::endl;
}

当遇到多个alignas指定符时,编译器将选择最严格的一个(取值最大的那一个)。

output: 16

附加说明 3

alignas不能用于使类型的对齐方式小于没有该声明时类型的对齐方式。

alignof 是什么意思

语法:

alignof( type-id )

返回一个std::size_t类型的值

相同定义具有 sizeof( type-id )

sizeofalignof有什么区别?

struct MyStruct
{
    int x;
    double y;
    char z;
};
    
main() 
{
    std::cout << "The sizeof(MyStruct): " << sizeof(MyStruct) << std::endl;
    std::cout << "The alignof(MyStruct): " << alignof(MyStruct) << std::endl;
}

output:
    The sizeof(MyStruct): 24
    The alignof(MyStruct): 8

结构体填充问题

结构体填充是C语言中的一个概念,它在内存地址之间添加一个或多个空字节,以便将数据对齐在内存中。

更多信息:C++ 中的结构体填充

附加说明

结果是一个std::size_t类型的常量表达式,即它可以在编译时计算出来。

更多信息请参见:来源1来源2


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接