c++内存对齐及相关函数
转载https://www.cnblogs.com/zhao-zongsheng/p/9099603.html
作者 赵宗晟
转载 https://blog.csdn.net/luoshabugui/article/details/83268086
转载:http://hi.baidu.com/freelonely/blog/item/340341077c4d287302088189.html
什么是内存对齐(memory alignment)
首先,什么是内存对齐(memory alignment)?这个是从硬件层面出现的概念。大家都知道,可执行程序是由一系列CPU指令构成的。CPU指令中有一些指令是需要访问内存的。最常见的就是“从内存读到寄存器”,以及“从寄存器写到内存”。在老的架构中(包括x86),也有一些运算的指令是可以直接以内存为操作数,那么这些指令也隐含了内存的读取。在很多CPU架构下,这些指令都要求操作的内存地址(更准确的说,操作内存的起始地址)能够被操作的内存大小整除,满足这个要求的内存访问叫做访问对齐的内存(aligned memory access),否则就是访问未对齐的内存(unaligned memory access)。举例来说,ARM的LDRH指令从内存中读取2个byte到寄存器中。如果指定的内存的地址是0x2587c20,因为0x2587c20这个数能够被2整除,所以这2个byte是对齐的。而如果指定的内存的地址是0x2587c33,因为不能被2整除,所以是未对齐的。
那如果访问未对齐的内存会出现什么结果呢?这个要看CPU。
有些CPU架构可以访问未对齐的内存,但是会有性能上的影响。典型的就是x86架构CPU
有些CPU会抛出异常
还有些CPU不会抛出任何异常,会静默地访问错误的地址
近几年也有些CPU的一部分指令可以正常访问未对齐的内存,同时不会有性能影响
因为每个CPU对未对齐内存的访问的处理方式都不一样,所以访问未对齐的内存是要尽量避免的。所以就出现了C/C++的内存对齐机制。
C++11 内存对齐实现
struct s3
{
char s;
double d;
int i;
};
struct s11
{
alignas(16) char s;
int i;
};
struct s12
{
alignas(16) char s;
int i;
};
// alignof
cout << "-------------------alignof---------------------" << endl;
// 基本对齐值
cout << "alignof(std::max_align_t) " << alignof(std::max_align_t) << endl;
cout << endl;
cout << "-------basic type" << endl;
cout << "alignof(char) " << alignof(char) << endl;
cout << "alignof(int) " << alignof(int) << endl;
cout << "alignof(double) " << alignof(double) << endl;
cout << endl;
cout << "-------struct" << endl;
cout << "alignof(s1) " << alignof(s1) << endl;
cout << "alignof(s2) " << alignof(s2) << endl;
cout << "alignof(s3) " << alignof(s3) << endl;
cout << endl;
cout << endl;
// alignas
cout << "-------------------alignas---------------------" << endl;
cout << "alignof(s1) " << alignof(s1) << endl;
cout << "alignof(s11) " << alignof(s11) << endl;
cout << "alignof(s12) " << alignof(s12) << endl;
cout << "sizeof(s1) " << sizeof(s1) << endl;
cout << "sizeof(s11) " << sizeof(s11) << endl;
cout << "sizeof(s12) " << sizeof(s12) << endl;
结果如下
预对齐内存的分配
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。在Linux中,这些函数返回的地址在32位系统是以8字节为边界对齐,在64位系统是以16字节为边界对齐的。有时候,对于更大的边界,例如页面,程序员需要动态的对齐。虽然动机是多种多样的,但最常见的是直接块I/O的缓存的对齐或者其它的软件对硬件的交互,因此,POSIX 1003.1d提供一个叫做posix_memalign( )的函数:
/* one or the other – either suffices */
#define _XOPEN_SOURCE 600
#define _GNU_SOURCE
#include <stdlib.h>
int posix_memalign (void **memptr,
size_t alignment,
size_t size);
- See http://perens.com/FreeSoftware/ElectricFence/ and http://valgrind.org, respectively.
调用posix_memalign( )成功时会返回size字节的动态内存,并且这块内存的地址是alignment的倍数。参数alignment必须是2的幂,还是void指针的大小的倍数。返回的内存块的地址放在了memptr里面,函数返回值是0.
调用失败时,没有内存会被分配,memptr的值没有被定义,返回如下错误码之一:
EINVAL
参数不是2的幂,或者不是void指针的倍数。
ENOMEM
没有足够的内存去满足函数的请求。
要注意的是,对于这个函数,errno不会被设置,只能通过返回值得到。
由posix_memalign( )获得的内存通过free( )释放。用法很简单:
char *buf;
int ret;
/* allocate 1 KB along a 256-byte boundary */
ret = posix_memalign (&buf, 256, 1024);
if (ret) {
fprintf (stderr, "posix_memalign: %s\n",
strerror (ret));
return -1;
}
/* use 'buf'... */
free (buf);
其它和对齐有关的
与对齐有关的问题的范围要超过标准类型的自然对齐和动态存储器地分配。例如,非标准和复杂的类型比标准类型有更复杂的要求。另外,对对齐的关注在给指向不同类型的指针赋值和使用强转时显得加倍的重要。
非标准类型。非标准和复杂的数据类型的对齐比简单的自然对齐有着更多的要求。这里四个有很有用的方法:
•一个结构的对齐要求是和它的成员中最大的那个类型一样的。例如,一个结构中最大的是以4字节对齐的32bit的整形,那么这个结构至少以4字节对齐。
•结构也引入了填充的需要,用来保证每一个成员都符合自己的对齐要求。所以,如果一个char (可能以1字节对齐)后跟着一个int (可能以4字节对齐),编译器会自动地插入3个字节作为填充来保证int以4字节对齐。
程序员有时候排列结构里面的成员-例如,以大小来递减-来是使用作填充的垃圾空间最少。GCC的选项- Wpadded能对这些努力有帮助,因为它使得在编译器偷偷插入填充时产生警告。
•一个联合的对齐和联合里最大的类型一样。
•一个数组的对齐和数组里的元素一样。所以,数组的对齐并不比单单的一个成员严格,这样能使数组里面的所有成员都是自然对齐的。
__attribute__选项
struct stu{
char sex;
int length;
char name[10];
};
struct stu my_stu;
由于在x86下,GCC默认按4字节对齐,它会在sex后面跟name后面分别填充三个和两个字节使length和整个结构体对齐。于是我们sizeof(my_stu)会得到长度为20,而不是15.
struct stu my_stu;
我们可以按照自己设定的对齐大小来编译程序,GNU使用__attribute__选项来设置,比如我们想让刚才的结构按一字节对齐,我们可以这样定义结构体
struct stu{
char sex;
int length;
char name[10];
}attribute ((aligned (1)));
则sizeof(my_stu)可以得到大小为15。