由内存布局引发的思考

最近再看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内存机制
1. 栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。2. 堆(Heap) :由程序员用malloc()/calloc()/realloc()分配空间,free()释放所申请的空间。如果程序员忘记free(),则会造成内存泄漏,程序结束时可能会由操作系统回收,也许就一直占用着直至关机。3. 全局区/静态区(Global Static Area):全局变量和静态变量存放区,程序一经编译好,该区域便存在。并且C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,由于编译器会给全局变量和静态变量自动初始化赋值,所以没有区分了)。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。4. C风格字符串常量存储区:专门存放字符串常量的地方,程序结束时释放。有的书也叫符号表。5. 程序代码区:存放程序二进制代码的区域。
c++

参考: http://blog.csdn.net/haoel/article/details/3081328

c/c++ 32位情况

参考 [http://www.cnblogs.com/ddatsh/archive/2010/12/07/1899303.html]

为什么会有内存对齐

以下内容节选自《Intel Architecture32Manual》。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

原文链接: https://www.cnblogs.com/Lelpuchcr/archive/2013/06/11/3131862.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/91885

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月10日 上午1:23
下一篇 2023年2月10日 上午1:24

相关推荐