一. 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++的指向子类对象的父类指针
先看一段代码:
- <pre name="code" class="cpp">#include "stdafx.h"
- class A
- {
- public:
- A(){a = 2;};
- void fun0()
- {
- printf("A::fun0 a = %d \n", a);
- }
- virtual int fun1()
- {
- printf("A::fun1 a = %d \n" , a);
- return a;
- }
- int a;
- };
- class B : public A
- {
- public:
- B(){
- a = 4;
- b = 111;
- };
- void fun0()
- {
- printf("B::fun0 a = %d \n" , a);
- }
- virtual int fun1()
- {
- printf("B::fun1 a = %d \n" , a);
- return a;
- }
- int a;
- int b;
- };
- class C
- {
- };
- class D
- {
- public:
- int M;
- virtual int getM()
- {
- return M;
- }
- };
- int main(int argc, char* argv[])
- {
- printf("sizeof(A) = %d \n" , sizeof(A));
- printf("sizeof(B) = %d \n" , sizeof(B));
- printf("sizeof(C) = %d \n" , sizeof(C));
- printf("---------------\n");
- A *pa = new B();
- pa->fun0();
- pa->fun1();
- printf("pa地址 = %X \n" , pa);
- printf("pa->a = %d \n" , pa->a);
- //printf("pa->b = %d \n" , pa->b);
- printf("---------------\n");
- B *pb = dynamic_cast<B *>(pa);
- pb->fun0();
- pb->fun1();
- printf("pb->a = %d \n" , pb->a);
- printf("---------------\n");
- D *d = (D*)pb;
- printf("D地址 = %X \n" , d);
- printf("d->getM() = %d \n\n" ,d->getM());
- printf("(d->M) = %d \n" ,d->M);
- printf("d->M 地址 %X \n\n" ,&(d->M));
- printf("pa->a = %d \n" , pa->a);
- printf("pa->a 地址 %X \n" , &(pa->a));
- delete pb;
- //delete pa;
- return 0;
- }</pre><br>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <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 = 2 |
B::a 父类的成员变量 a = 4 |
B::b 父类的成员变量 b = 111 |
这时,
- pa->fun0();
然后我的理解是this指针作为参数传递给fun0, 其实传递的是内存数据段的首地址(也就是上面虚函数指针的地址) .
输出的a = 2时, 其实A::fun0()在编译的时候, 已经确定a的值, 是首地址+1的值. 也就是 A::a 父类的成员变量的 a = 2了.
- pa->fun1();
同样, B::fun1() , 在编译完的时候 就已经 确定里面涉及的 a值 是 首地址+2了. 也就是 B::a 父类的成员变量 a = 4.
- pa->a;
- //printf("pa->b = %d \n" , pa->b);
首先sizeof(A) = 8. 虽然传来的首地址可用内存是sizeof(B) =16 .
但是编译器认为A的对象就是8. 所以下面的内存
B::a 父类的成员变量 a = 4 |
B::b 父类的成员变量 b = 111 |
对于编译器来说是越界的. 所以也找不到B里面b...
或者如果没有子类的干扰的话 , 直接就能看出int b 就不属于类A.
- B *pb = dynamic_cast<B *>(pa);
然后 父类的指针指向 转换成子类指针. 当然强制转换也可以的. B *pb = (B *)(pa);但是这样是不安全的. 类B 中的最大偏移为3. 如果pa是指向的是父类的对象, 最大偏移量是1 .
在运行时将会出现读取内存越界的情况. static_cast 也是只取到pa的地址, 结果也是相同的情况 ,
- A * a = new A();
- B *pb = (B *)(a);
- pb->fun0();
- pb->fun1();
- printf("pb->a = %d \n" , pb->a);
dynamic_cast 是动态转换, 在运行时类的类型将会与子类匹配, 如果pa 是指向子类的指针, 则返回pa, 否则返回NULL.
下面的代码, 意思和上面差不多, 就不一一解释了.关于D类型的代码, 下面还会继续解释.
总结:
1. 我的理解, this就是指向对象数据段的首地址
2. 类的成员函数编译时把成员变量编译成对this指针的偏移量.
3. 虚函数表的指针地址就是this的值.
4. 验证.还是上面的代码, 类D和A的类结构相同.
- d->getM();
pd的M值和pa的a值相同, 地址相同.
这个就这么多了...