【内存对齐(二)】__declspec( align(#) )的用法和大小计算
http://www.cppblog.com/deercoder/archive/2011/03/13/141747.html
在上面讲到了关于pack的内存对齐和计算方法,这里继续讲实现内存对齐的另一种方式:__declspec( align(#) )
__declspec( align(#) )和#pragma pack( n )有密切联系。
当一个变量或结构体同时受两者影响时,前者的优先级高。
成员的地址决定于前者及后者,其要么是前者的倍数,要么是后者的倍数,要么是成员的大小的倍数,取最小。
结构体最后的大小于前者有关,其要么是前者的倍数,要么是结构体中最大偏移量的倍数,取最大。
要算出最后结果,必须知道两者的值或缺省值。
下面举一个例子来详细的分析:
#include <stdio.h>
#include "stdafx.h"
#include <stdlib.h>
//using namespace std;
#pragma pack( push, 4 )
__declspec( align(32) )struct D
{
int i1;
double d1;
int i2;
int i3;
};
int main()
{
cout << "sizeof(int) = "<<sizeof(int) << endl;
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(D) << endl;
system("PAUSE");
return 0;
}
这段代码在VS 2010中的运行结果是,sizeof(D)的大小为32,而在Dev C++,C-Free 5.0以及gcc中的结果都似乎20。下面我们来着重讲讲关于__declspec( align(#) )的用法:
正如前面所说的,当有__declspec( align(#) )和pack的时候,__declspec( align(#) )的优先级要高些。所以对于上面这个例子,我们首先来计算出来每一个的大小。
1. 成员的地址如何取?
规则:成员的地址要取pack(n),__declspec( align(m) ),以及成员自身大小这三者之间的最小值,也就是,min(n,m,sizeof(成员变量类型)),那么我们可以对每一个结构体的成员都进行分析。
第一个为int类型,占据4B,所以地址是[0~3].
第二个为double类型,它的地址要根据min(4,32,sizeof(double))来判断,所以应该是4的倍数,也就是相邻着int类型的i1存放。地址是[4~11]。
第三个为int类型,占据4B,同样应该是4的倍数,地址是[12~15].
第四个为int类型,占据4B,地址为[16~19].
从而总的地址是从[0~19]连续存放的20个字节,那么是否sizeof(D)的大小就是20呢?
经过测试,我们可以看到,在VS 2010中,结果是32,why?
这就要用__declspec( align(#) )来解释了。也就是下面第二点的内容。
2. 结构体最后的大小如何决定?
规则:结构体最后的大小与__declspec( align(m) )有关,其要么是它的倍数,要么是结构体中最大偏移量的倍数,取最大。
根据这个规则,这里align是32,而结构体中最大的是double类型,也就是应该是max(32,8)=32,所以最后结构体的大小应该是32的倍数,而明显上面我们看到的实际大小是20B,从而需要扩展到32B。
在这里,就体现了__declspec( align(m) )的强大作用!
同样的,为了体现该语句的作用,我们去掉这个语句,运用我们前面一节内容的知识,来计算并测试sizeof(D),最终不论是在VS 2010还是Dev C++中,运行的结果都是上面我们所预测的20B。
OK,下面回到最后的疑问,也就是前面我们提出的,为何加入了__declspec( align(m) )语句之后,在DevC++和VS 2010的结果不同?
实际上,对于这些内存对齐的处理,不同的编译器可能采取不同的处理,就像前面一节中所说的,我将pack误用为package,导致根本没有达到按照我要求的字节对齐的目的,而且编译器根本不提供任何警告信息。那么,这里合理的解释是:Dev C++不支持这种用法。
通过查阅资料,参照这篇文章【 SSE指令介绍及其C、C++应用 】(http://blog.csdn.net/delphihero/archive/2006/09/24/1270069.aspx),我们可以看到作者有这么一段话:
“接下来我举一个例子来说明SSE的指令函数是如何使用的,必须要说明的是我以下的代码都是在VC7.1的平台上写的,不保证对其它如Dev-C++、Borland C++等开发平台的完全兼容。”
“这里要注意一下,我使用了__declspec(align(16))做为数组定义的修释符,这表示该数组是以16字节为边界对齐的,因为SSE指令只能支持这种格式的内存数据。
我们在这里看到了SSE算的强大,相信它会成为多媒体程序员手中用来对付无穷尽流媒体数据的一把利剑。我后面还会写一些关于SSE算法更复杂应用的文章,敬请关注,感谢您抽时间阅读!
”
从这篇文章我们可以看到,SSE指令集的情况下,在VC 7.1下才支持__declspec(align(16))这种用法,而对于其他平台不一定有效。而前面我们使用的Dev C++以及C-Free,都是基于g++或者MinGW,不一定会支持这种方式,或者说,不一定按照这种内存对齐的建议来做,也就造成了结果的不同。
下面我们来继续探讨结构体中有结构体的情况。
先看看下面这段代码:
#include <stdio.h>
#include "stdafx.h"
#include <stdlib.h>
//using namespace std;
#pragma pack( push, 4 )
__declspec( align(32) )struct D
{
int i1;
double d1;
int i2;
int i3;
};
__declspec( align(16) ) struct E
{
int i1;
D m_d;
int i2;
};
int main()
{
cout << "sizeof(int) = "<<sizeof(int) << endl;
cout << "sizeof(char) = " << sizeof(char) << endl;
cout << "sizeof(double) = " << sizeof(double) << endl;
cout << sizeof(D) << endl;
cout << sizeof(E) << endl;
system("PAUSE");
return 0;
}
最后运行的结果是sizeof(E)为96,为何会是这个结果呢?我们来详细讲解下。
对于结构体E,第一个元素为int类型,所以占据[0~3]地址单元。
第二个元素是一个结构体,该结构体由于受上面__declspec( align(32) )的影响,优先级高,所以起始地址是32的倍数,而且大小为32B,从而应该放置在[32~63]单元处。
最后一个是int类型的变量,大小为4,所以应该是4的倍数,地址为[64~67]。
故结构体E的大小应该是从[0~67],占据68B,而由于前面还有限制__declspec( align(16) ),同时成员变量的最大偏移是sizeof(D)=32,所以我们最后这个结构体的大小应该是他们中最大值的倍数,也就是32的倍数,68向上取32的倍数应该是96.故结果为96.
最后仍然是上面平台的问题,在Dev C++和G++下面的结果不同,原因上面解释了。
MSDN:
“The sizeof value for any structure is the offset of the final member, plus that member's size, rounded up to the nearest multiple of the largest member alignment value or the whole structure alignment value, whichever is greater.”
中文:
“sizeof的结果都是结构体中最后的一个成员变量加上它的大小,再加上一个填充容量(padding),这个填充大小是成员变量最大的一个对齐参数或整个结构体的对齐参数的倍数,取哪个决定于哪个对齐参数较大”
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/e4209cbb-5437-4b53-b3fe-ac264501d404.htm
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vclang/html/9cb63f58-658b-4425-ac47-af8eabfc5878.htm
P.S.:上面是关于内存对齐的研究,如有谬误,欢迎指出!
附参考资料和拓展:
1. #pragma pack :http://blog.sina.com.cn/s/blog_492aa57901008y3h.html
2. #pragma pack( n )和__declspec( align(#) ) 的偏移量计算方法: http://blog.csdn.net/whoismickey/archive/2009/03/28/4032155.aspx
3. #pragma pack(push,1) (pop) :http://blog.csdn.net/jiang1013nan/archive/2009/11/25/4861248.aspx
4. 关于pragma pack的用法(四) C++中的内存对齐问题: http://www.cppblog.com/xczhang/archive/2007/12/23/39396.html
5. SSE指令介绍及其C、C++应用:http://blog.csdn.net/delphihero/archive/2006/09/24/1270069.aspx
6. c++中__declspec用法总结: http://sealbird.javaeye.com/blog/855096