由内存布局引发的思考
最近再看jvm的对象内存结构[http://www.importnew.com/1305.html]
其中又这么一段:
如果JVM并没有打乱属性的声明顺序,其对象内存布局将会是下面这个样子:
1 [HEADER: 8 bytes] 8 2 [a: 1 byte ] 9 3 [padding: 3 bytes] 12 4 [c: 4 bytes] 16 5 [d: 1 byte ] 17 6 [padding: 7 bytes] 24 7 [e: 8 bytes] 32 8 [f: 4 bytes] 36 9 [padding: 4 bytes] 40此时,用于占位的14个字节是浪费的,这个对象一共使用了40个字节的内存空间。但是,如果用上面的规则对这些对象重新排序,其内存结果会变成下面这个样子:
[HEADER: 8 bytes] 8 [e: 8 bytes] 16 [c: 4 bytes] 20 [a: 1 byte ] 21 [d: 1 byte ] 22 [padding: 2 bytes] 24 [f: 4 bytes] 28 [padding: 4 bytes] 32
文章中是以32位的机器举例,默认一次只能传送4个byte的数据,所以如果不做优化将会浪费一定的时间和空间。
那c/c++ 的内存布局如何?
以下转载自:[http://www.cnblogs.com/zhaoyl/archive/2012/10/05/2712519.html]
C内存机制
c++
参考: http://blog.csdn.net/haoel/article/details/3081328
c/c++ 32位情况
参考 [http://www.cnblogs.com/ddatsh/archive/2010/12/07/1899303.html]
为什么会有内存对齐
以下内容节选自《Intel Architecture 32 Manual》。 32bit = 4byte
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
1 #include<stdio.h> 2 3 typedef struct foo 4 { 5 char c1; 6 short s; 7 char c2; 8 int i; 9 }foo; 10 typedef struct foo1 11 { 12 char c1; 13 char c2; 14 short s; 15 int i; 16 }foo1; 17 18 int main(){ 19 20 struct foo a; 21 printf("c1\t%p\ns\t%p\nc2\t%p\ni\t%p\n", 22 (unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a, 23 (unsigned int)(void*)&a.s - (unsigned int)(void*)&a, 24 (unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a, 25 (unsigned int)(void*)&a.i - (unsigned int)(void*)&a); 26 printf("sizeof(foo)=%d\n",sizeof(foo)); 27 28 struct foo1 b; 29 printf("c1\t%p\nc2\t%p\ns\t%p\ni\t%p\n", 30 (unsigned int)(void*)&b.c1 - (unsigned int)(void*)&b, 31 (unsigned int)(void*)&b.c2 - (unsigned int)(void*)&b, 32 (unsigned int)(void*)&b.s - (unsigned int)(void*)&b, 33 (unsigned int)(void*)&b.i - (unsigned int)(void*)&b); 34 printf("sizeof(foo)=%d\n",sizeof(foo1)); 35 return 0; 36 }
结果:
c1 0x0 s 0x2 c2 0x4 i 0x8 sizeof(foo)=12 c1 0x0 c2 0x1 s 0x2 i 0x4 sizeof(foo)=8
总结,对于32位cpu,首先是不能出现奇数字节与偶数字节交叉情况,然后尽量让字节组成单行4字节。
通过对齐系数做自定义 [http://blog.csdn.net/cuibo1123/article/details/2547442]
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员
自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
#pragma pack(1) typedef struct foo { char c1; short s; char c2; int i; }foo; #pragma pack()
在这中情况下,按照一个字节对齐
c1 0x0 s 0x1 c2 0x3 i 0x4 sizeof(foo)=8
按照2个字节对齐,
#pragma pack(2)
c1 0x0 s 0x2 c2 0x4 i 0x6 sizeof(foo)=10