1.什么是内存对齐
举个例子介绍下内存对齐,有兴趣的小伙伴可以自己运行如下的示例,看看由char,short,int,double组成的结构体A,B,C占用的内存大小是多少?
#include <iostream>
struct A
{
int a;
short b;
char c;
double d;
};
struct B
{
int a;
char b;
short c;
double d;
};
struct C
{
char a;
int b;
double c;
short d;
};
int main()
{
printf("%d %d %d", sizeof(A), sizeof(B), sizeof(C));
}
简单地说,每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。一般默认的对齐系数是4个字节。当然,程序员也可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
对齐规则:
(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
(3) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
有关结构体字节对齐方式设置 #pragma pack 用法
#pragma pack (n) // 作用:C编译器将按照n个字节对齐。
#pragma pack () // 作用:取消自定义字节对齐方式。
#pragma pack (push,n) // 作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为n个字节对齐
#pragma pack(pop) // 作用:恢复对齐状态
因此可见,加入push和pop可以使对齐恢复到原来状态,而不是编译器默认,可以说后者更优,但是很多时候两者差别不大
举个示例
#pragma pack (1) // 设置对齐方式
struct A
{
int a;
char b;
short c;
double d;
};
#pragma pack () // 取消自定义对齐方式
#pragma pack (push, 1) // 设置对齐方式
struct B
{
int a;
char b;
short c;
double d;
};
#pragma pack (pop) // 恢复对齐方式
2.
为什么要进行内存对齐
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.
现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。
假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.
简单的说内存对齐能够提高 cpu 读取数据的速度,减少 cpu 访问数据的出错性(有些 cpu 必须内存对齐,否则指针访问会出错)
。
上面介绍了#pragma pack()方法实现内存对齐。接下来介绍C++11中相关内存对其的方法。
3.C++11 内存对齐的新属性
3.1 alignas
alignas指定内存对其大小,有时候我们希望不按照默认的内存对齐方式来对齐,这时我们可以用alignas来指定内存对齐。
在C++11中,只要是一个编译期数值(#define, static const, template)都支持alignas,另外需要注意alignas只能改大不能改小,如果要改小可以使用上面提到的#pragma pack(1)
3.2 alignof 和 std::alignment_of
alignof用来获取内存对齐大小,用法比较简单:
A a;
cout << alignof(a) << endl;
alignof只能返回一个size_t,而std::alignment_of继承自std::integral_constant,拥有value_type,type,value成员
cout << std::alignment_of<A>::value << endl; >>>> 1
cout << std::alignment_of<B>::value << endl; >>>> 2
3.3 std::aligned_storage
std::aligned_storage可以看成一个内存对其的缓冲区,原型如下:
template<std::size_t Len, std::size_t Align = /*default-alignment*/>
struct aligned_storage;
Len表示所存储类型的sie,Align表示该类型的内存对齐大小
3.4 max_align_t 和 std::align
std::max_align_t用来返回当前平台的最大默认内存对齐类型,对于malloc返回的内存,其对齐和max_align_t类型的对齐大小应当是一致的。我们可以通过下面的方式获得当前平台的最大默认内存对齐数:
std::cout << alignof(std::max_align_t) << std::endl;
std::align用来在一大块内存中获取一个符合指定内存要求的地址
char buffer[] = "......";
void *ptr = buffer;
std::size_t space = sizeof(buffer) - 1;
std::align(alignof(int),sizeof(char),pt,space);