C++对象中数据成员的内存分布

来源:互联网 发布:数据连接自动开启 编辑:程序博客网 时间:2024/06/16 00:00
原文:http://ocelot1985-163-com.iteye.com/blog/990907 

下面我们再来在 C++类中内存分布情况。
C++代码 

class c1   {   public:         static int nCount;         int nValue;         char c;         c1();         virtual ~c1();         int getValue(void);         virtual void foo(void);         static void addCount();   }  


我们可以通过 sizeof()得到 c1 对象的大小为 12 个字节。
1、 函数 c1,~c1(),getValue,foo,addCount 为函数,其位于程序的代码段,多个对象共享,
    因此不算在 c1 的 size 中。
2、 static int nCount,因为该变量为静态变量,在 c1 所定义的对象之间共享,其位于程序的
    数据段。其不会随着对象数据的增加而增加。
3、 nValue 和 c 占据内存,其中 nValue 使用了 4 个字节,c 虽然使用了 1 个字节,但由于内
    存对齐的缘故,其也使用了 4 个字节,这样总共占据了 8 个字节。
4、 因为有虚函数,每个类对象要有一个指向虚函数表的指针,每个对象一个,占据 4 个字
    节,虚函数表是位于程序的代码段。
这样 c1 对象的大小为 12 个字节。
总结一下:
1、 静态成员和非静态成员函数,主要占据代码段内存,生成对象,不会再占用内存。
2、 非静态数据成员是影响对象占据内存大小的主要因素,随着对象数据的增加,非静态数
    据成员占据的内存会相应增加。
3、 所有的对象共享一份静态数据成员,所以静态数据成员占据的内存的数量不会随着对象
    的数据的增加而增加。
4、 如果对象中包含虚函数,会增加 4 个字节的空间,不论是有多少个虚函数。
对于 C++中的非内置类型的全局变量,其是属于.data 还是.bss 呢?
C++代码 

#include <stdio.h>   #include <stdlib.h>   #include <unistd.h>   class c1   {   public:        c1();        c1(int i);        ~c1();        int n1;   };      c1::c1()   {       n1=0;       printf("n1=%d\n",n1);   };   c1::c1(int i)   {       n1=i;       printf("n1=%d\n",n1);   };   c1::~c1()   {       ;   }   c1 g1;   c1 g2=10;   int main()   {         pause();         return 0;   }  

这是一个 C++的例子,我的问题是 g1 和 g2 分别在哪一个节呢?
按照我们原来的标准,未赋初值的全局变量 g1,将位于.bss 节,赋初值了的 g2,将位于.data
节。可细一想,又不对劲,非内置类型的全局对象,需要调用构造函数将其构造出来,不能
只通过 mmap 将其映射到内存就可以完成的。
头有些大了。
下面我来回答这个问题,实际上 g1 和 g2 全部位于.bss 节,编译器只是为其划分出了一段内
存空间。
我们来验证一下:
>nm –f sysv hello

g1                           |00010a08|  B | OBJECT|00000004| |.bss g2                           |00010a0c|  B | OBJECT|00000004| |.bss 

那什么时候,对对象的成员变量赋值呢?
我们先来运行一下进程。

./hello
n1=0
n1=10
在上面的程序中,main 函数的第一句是 pause(),所以 main 函数刚一进入就停住了,而我们
依然能够看 g1 和 g2 的构造函数打印出来的结果,很显然进入 main 函数之前,运行了 g1
和 g2 的构造函数。
还记得我们前面提到的.init_array 节吗,loader 在将程序焦点转移到 main 函数之前,其会运
行.init_array 函数指针数组中的所有函数。
让我们来查看一下.init_array 中都有那些内容。
>objdump -s hello .

................
Contents of section .init_array:
  108fc 7c840000 14870000
.............................................

 

108fc 是内存地址的一个序号,我们可以不用管它。7c840000 14870000 才是 init_array 中真 正的内容。 在这里是以小端排序,我们试着翻译一下:                  

 应该为 7c840000                        0000847c                

   应该为 14870000                        00008714

我们可以通过查看符号表,看看这两个地址都对应着什么内容。

 >nm -n -C hello

0000847c t frame_dummy 000084a4 T c1::c1() 000084e8 T c1::c1() 0000852c T c1::c1(int) 00008574 T c1::c1(int) 000085bc T c1::~c1() 000085e0 T c1::~c1() 00008604 T main 0000861c t __static_initialization_and_destruction_0(int, int) 000086c4 t __tcf_1 000086ec t __tcf_0 00008714 t global constructors keyed to _ZN2c1C2Ev 00008734 T __libc_csu_init 



这样就很清楚了,               进程运行时,         在调用 main 之前,  要运行 frame_dummy 和 global constructors
Blog: http://blog.chinaunix.net/u/30686/                                               69
Email:loughsky@sina.com
keyed to _ZN2c1C2Ev。
如果还有兴趣的朋友,可以尝试着对进程进行反编译看看这两个函数到底做了什么事。
我们前文说到,对于 C 程序编写的进程来讲,在运行时,只是通过 mmap 为其数据段分配
了一段虚拟内存,只有在实际用到才会分配物理内存。
而对于 C++编写的程序来讲,那些非内置类型的全局变量,由于在 main 函数之前,需要运
行构造函数,为其成员变量赋值,这时虽然在你的程序里还没有用到,但它已经开始占用了
物理内存。