c++内存分配

来源:互联网 发布:软件架构和软件结构 编辑:程序博客网 时间:2024/04/27 13:44

一.  c++的内存分配:

http://blog.sina.com.cn/s/blog_3cba7ec10100hh6p.html

1、高位地址:栈(存放着局部变量和函数参数等数据),向下生长   (可读可写可执行) 
2、                   堆(给动态分配内存时使用),向上生长             (可读可写可执行) 
3、                   数据段(保存全局数据和静态数据)                    (可读可写不可执行) 
4、低位地址:代码段(保存代码)                                (可读可执行不可写) 

代码段就是存储程序文本的,所以有时候也叫做文本段,指令指针中的指令就是从这里取得。这个段一般是可以被共享的,比如你在Linux开了2个Vi来编辑文本,那么一般来说这两个Vi是共享一个代码段的,但是数据段不同(这点有点类似C++中类的不同对象共享相同成员函数)。 

数据段是存储数据用的,还可以分成初始化为非零的数据区,BSS,和堆(Heap)三个区域。初始化非零数据区域一般存放静态非零数据和全局的非零数据。BSS是Block Started by Symbol的缩写,原本是汇编语言中的术语。该区域主要存放未初始化的全局数据和静态数据。还有就是堆了,这个区域是给动态分配内存是使用的,也就是用malloc等函数分配的内存就是在这个区域里的。它的地址是向上增长的。C里面区分初始化和非初始化, C++里面不区分了.  
常量存储区  这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改.


最后一个堆栈段(注意,堆栈是Stack,堆是Heap,不是同一个东西),堆栈可太重要了,这里存放着局部变量和函数参数等数据。例如递归算法就是靠栈实现的。栈的地址是向下增长的。有些资料还有
这么一说(自由存储区) 就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的.

========高地址   ======= 
程序栈 ,堆栈段 (向下增长) 内存地址减小的方向增长
============== 
堆 (向上增长) 向着内存地址增加的方向

============== 
BSS 
------ 
非零数据
 
=========低地址   ======= 
=========       ======= 
代码           代码段 
=========       ======= 

 需要注意的是,代码段和数据段之间有明确的分隔,但是数据段和堆栈段之间没有,而且栈是向下增长,堆是向上增长的,因此理论上来说堆和栈会“增长到一起”,但是操作系统会防止这样的错误发生,所以不用过分担心。

二.  c++的 this 

对于类,编译器会自动为其生成五个隐式成员函数。分别为

1.  默认构造函数

2.  默认析构函数

3.  赋值操作符

4.  复制构造函数

5.   this指针

对于this 指针 http://lwzy-crack.blog.163.com/blog/static/95272042200962523450519/

http://www.cnblogs.com/st_zhang/archive/2010/09/07/1820488.html

this指针并不是对象本身的一部分,不会影响sizeof(“对象”)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

其实this 只是作为类的非静态成员函数的参数.并不是类内部的隐藏的成员变量.

静态成员函数没有this指针.

类的对象都有数据段 和 代码段. 但是代码段是公用的...通过非静态成员函数的参数 this , 就能确保每个对象调用的自己的成员变量.

(空类的sizeof = 1. 有虚函数的话 sizeof再加上虚函数表的指针的大小4.)

三.  c++的重载技术 

先说extern “C”.

在C++中,函数void foo( int x, int y )与void foo( int x, float y)编译生成的符号是不相同的,后者为_foo_int_float。

void foo( int x, int y );

  该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangledname”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y)编译生成的符号是不相同的,后者为_foo_int_float。

四.  c++的父类 子类 内存分配

下面就单继承分为几种情况阐述:
1.普通继承+父类无virtual函数
    若子类没有新定义virtual函数  此时子类的布局是 : 由低地址->高地址  为父类的元素(没有vptr),子类的元 素(没有vptr).
   若子类有新定义virtual函数  此时子类的布局是 : 由低地址->高地址  为父类的 元素(没有vptr),子类的元 素(包含vptr,指向vtable.)


2. 普通继承+父类有virtual函数
    不管子类没有新定义virtual函数  此时子类的布局是 : 由低地址->高地址  为父类的 元素(包含vptr), 子类的元 素.
    如果子类 有新定义的virtual函数,那么在父 类的vptr(也就是第 一个vptr)对应的vtable中添加一 个函数指针.

vptr    (子类和父类的虚函数表指针)父类的成员变量子类的成员变量

五.  c++的指向子类对象的父类指针

先看一段代码: 

[cpp] view plaincopy
  1. <pre name="code" class="cpp">#include "stdafx.h"  
  2. class A   
  3. {  
  4. public:  
  5.     A(){a = 2;};  
  6.   
  7.   
  8.     void fun0()  
  9.     {  
  10.         printf("A::fun0 a = %d \n", a);  
  11.     }  
  12.   
  13.   
  14.     virtual int fun1()  
  15.     {  
  16.         printf("A::fun1 a = %d \n" , a);  
  17.         return a;  
  18.     }  
  19.   
  20.   
  21.     int a;  
  22.       
  23. };  
  24. class B : public A  
  25. {  
  26. public:  
  27.     B(){  
  28.         a = 4;  
  29.         b = 111;  
  30.     };  
  31.   
  32.   
  33.     void fun0()  
  34.     {  
  35.         printf("B::fun0 a = %d \n" , a);  
  36.     }  
  37.   
  38.   
  39.     virtual int fun1()  
  40.     {  
  41.         printf("B::fun1 a = %d \n" , a);  
  42.         return a;  
  43.     }  
  44.   
  45.   
  46.     int a;  
  47.     int b;  
  48. };  
  49. class C   
  50. {  
  51.   
  52.   
  53. };  
  54. class D   
  55. {  
  56. public:  
  57.     int M;  
  58.     virtual int getM()  
  59.     {  
  60.       return M;  
  61.     }  
  62. };  
  63. int main(int argc, char* argv[])  
  64. {  
  65.     printf("sizeof(A) = %d \n" , sizeof(A));  
  66.     printf("sizeof(B) = %d \n" , sizeof(B));  
  67.     printf("sizeof(C) = %d \n" , sizeof(C));  
  68.     printf("---------------\n");  
  69.   
  70.     A *pa = new B();  
  71.     pa->fun0();  
  72.     pa->fun1();  
  73.     printf("pa地址 = %X \n" , pa);  
  74.     printf("pa->a = %d \n" , pa->a);  
  75.     //printf("pa->b = %d \n" , pa->b);  
  76.   
  77.     printf("---------------\n");  
  78.     B *pb =  dynamic_cast<B *>(pa);  
  79.     pb->fun0();  
  80.     pb->fun1();  
  81.   
  82.     printf("pb->a = %d \n" , pb->a);  
  83.   
  84.     printf("---------------\n");  
  85.     D *d = (D*)pb;  
  86.     printf("D地址 = %X \n" , d);  
  87.     printf("d->getM() = %d \n\n" ,d->getM());  
  88.   
  89.     printf("(d->M) = %d \n" ,d->M);  
  90.     printf("d->M 地址 %X \n\n" ,&(d->M));  
  91.   
  92.     printf("pa->a = %d \n" , pa->a);  
  93.     printf("pa->a 地址 %X \n" , &(pa->a));  
  94.       
  95.     delete pb;  
  96.     //delete pa;  
  97.     return 0;  
  98. }</pre><br>  
  99. <pre></pre>  
  100. <pre></pre>  
  101. <pre></pre>  
  102. <pre></pre>  
  103. <pre></pre>  
  104. <pre></pre>  
  105. <pre></pre>  
  106. <pre></pre>  
  107. <pre></pre>  
  108. <pre></pre>  
  109. <pre></pre>  
  110. <pre></pre>  
  111. <pre></pre>  

运行结果


 代码解读.在main中....

 1.  sizeof(A) = 8. A类中 有虚函数表指针vptr 再加上 int a的值 = sizeof(int). 4 + 4 =8;

 2.  sizeof(B) =16 B类的大小 是  sizeof(A) + 2 *sizeof(int) = 16.
 3.  sizeof(C) = 1 空类的大小为1.

 4. A *pa = new B(); 父类的指针指向了子类的对象. 

       这时pa用的代码段是A::fun0(), A::fun1(); 

pa是指向子类对象的首地址, 用的数据段是子类对象内存数据.如下.

vptr (虚函数指针)指向B::fun1()A::a    父类的成员变量 a = 2B::a        父类的成员变量 a = 4B::b        父类的成员变量 b = 111

这时, 

[cpp] view plaincopy
  1. pa->fun0();  
调用的是pa的代码段 A::fun0();

       然后我的理解是this指针作为参数传递给fun0, 其实传递的是内存数据段的首地址(也就是上面虚函数指针的地址) .

       输出的a = 2时,  其实A::fun0()在编译的时候, 已经确定a的值, 是首地址+1的值. 也就是  A::a    父类的成员变量的 a = 2了.

[cpp] view plaincopy
  1. pa->fun1();  
是虚函数, 运行时动态绑定 通过数据段中的vptr获取虚函数 B::fun1() .

同样, B::fun1() ,  在编译完的时候 就已经 确定里面涉及的 a值 是 首地址+2了. 也就是  B::a        父类的成员变量 a = 4.

[cpp] view plaincopy
  1. pa->a;  
其实在编译的时候,  编译器并不知道pa指向的是子类对象.  编译器编译后变成首地址+1的值.
[cpp] view plaincopy
  1. //printf("pa->b = %d \n" , pa->b);  
屏掉的这句,  去掉注释将会出现编译错误.  error C2039: 'b' : is not a member of 'A'. 

首先sizeof(A) = 8. 虽然传来的首地址可用内存是sizeof(B) =16 .

但是编译器认为A的对象就是8. 所以下面的内存

B::a        父类的成员变量 a = 4B::b        父类的成员变量 b = 111

对于编译器来说是越界的. 所以也找不到B里面b...

或者如果没有子类的干扰的话 , 直接就能看出int b 就不属于类A.

[cpp] view plaincopy
  1. B *pb =  dynamic_cast<B *>(pa);  
首先 用到dynamic_cast时, 要在工程的设置里面. 找到C+ ->C++语言->允许RTTI.

然后 父类的指针指向 转换成子类指针. 当然强制转换也可以的. B *pb =  (B *)(pa);但是这样是不安全的. 类B 中的最大偏移为3. 如果pa是指向的是父类的对象, 最大偏移量是1 .

在运行时将会出现读取内存越界的情况. static_cast  也是只取到pa的地址, 结果也是相同的情况 , 

[cpp] view plaincopy
  1. A * a = new A();  
  2. B *pb = (B *)(a);  
  3. pb->fun0();  
  4. pb->fun1();  
  5. printf("pb->a = %d \n" , pb->a);  

dynamic_cast 是动态转换, 在运行时类的类型将会与子类匹配, 如果pa 是指向子类的指针, 则返回pa, 否则返回NULL.

下面的代码, 意思和上面差不多, 就不一一解释了.关于D类型的代码, 下面还会继续解释.

总结:

1. 我的理解, this就是指向对象数据段的首地址

2. 类的成员函数编译时把成员变量编译成对this指针的偏移量.

3. 虚函数表的指针地址就是this的值.

4. 验证.还是上面的代码, 类D和A的类结构相同. 

[cpp] view plaincopy
  1. d->getM();  
取的还是B::fun1(). 调用了pb的虚函数表.

pd的M值和pa的a值相同, 地址相同.

这个就这么多了...

0 0
原创粉丝点击