c++类内存机制探寻

class的成员变量分为static 和nonstatic两类

class的成员方法分为static nonstatic 和virtual三类。

class实例化的过程中,实际上实例化的是类的nonstatic成员变量,这也决定了类的内存消耗。

继承分为单继承 多继承和虚拟继承。

对子类的实例化,会先对父类进行实例化,子类是如何引用父类的,是用指针还是直接包含类的实现,因为每一个子类的父类实际上不是共享的,因此子类的父类应该是包含在子类的内存域中。

多继承和虚拟继承的差别:公共父类是否会被实例化多次。虚拟继承使用了base class table的间接引用的模型。


每一个table,就意味着一次查找。


三种设计模式:

1 面向过程

2 抽象数据类型,将数据使用接口封装,因此就抽象了

3 面向对象,对象的接口是虚表,因此接口的特性随着类的具体实例化而


类的继承之内存排布:

1 首先是虚表指针,

2 父类的非静态成员变量

3 子类的非静态成员变量


如下面的代码:

#include <iostream>
using namespace std;
class B{
public:
virtual void foo(){
  cout<< "B foo";
};
int i;
int j[100];
};
class A:public B{
 public:
 virtual void foo(){
   cout << "A foo";
 }
 int i;
 int j[200];
};


int main(void){
  A a;
  cout<<&a;
  a.foo();


  B * pa=&a;
  pa->foo();
}

a的内存的大小包括了父类的i,j和自己的i,j,a的地址起始处是虚表指针。

pa这个函数指针在调用的过程中,编译器不会制定具体掉的哪个函数,只会续表指向的地址,放入到一个寄存器中,再call。


反汇编的结果如下:

(gdb) disassemble main
Dump of assembler code for function main():
   0x0000000000400940 <+0>: push   %rbp
   0x0000000000400941 <+1>: mov    %rsp,%rbp
   0x0000000000400944 <+4>: sub    $0x4d0,%rsp
   0x000000000040094b <+11>: lea    -0x4d0(%rbp),%rax
   0x0000000000400952 <+18>: mov    %rax,%rdi
   0x0000000000400955 <+21>: callq  0x400a50 <A::A()>
   0x000000000040095a <+26>: lea    -0x4d0(%rbp),%rax
   0x0000000000400961 <+33>: mov    %rax,%rsi
   0x0000000000400964 <+36>: mov    $0x6012e0,%edi
   0x0000000000400969 <+41>: callq  0x400800 <_ZNSolsEPKv@plt>
   0x000000000040096e <+46>: lea    -0x4d0(%rbp),%rax
   0x0000000000400975 <+53>: mov    %rax,%rdi
   0x0000000000400978 <+56>: callq  0x400a1c <A::foo()>
   0x000000000040097d <+61>: lea    -0x4d0(%rbp),%rax
   0x0000000000400984 <+68>: mov    %rax,-0x8(%rbp)
=> 0x0000000000400988 <+72>: mov    -0x8(%rbp),%rax
   0x000000000040098c <+76>: mov    (%rax),%rax
   0x000000000040098f <+79>: mov    (%rax),%rax
   0x0000000000400992 <+82>: mov    -0x8(%rbp),%rdx
   0x0000000000400996 <+86>: mov    %rdx,%rdi
   0x0000000000400999 <+89>: callq  *%rax                                                        /这就是多态
   0x000000000040099b <+91>: mov    $0x0,%eax
   0x00000000004009a0 <+96>: jmp    0x4009aa <main()+106>
   0x00000000004009a2 <+98>: mov    %rax,%rdi
   0x00000000004009a5 <+101>: callq  0x400820 <_Unwind_Resume@plt>
   0x00000000004009aa <+106>: leaveq 
   0x00000000004009ab <+107>: retq   
End of assembler dump.


在汇编看来,多态,就是一次间接寻址而已。


类的虚表示独立于类的实例的,因此一个类的拷贝构造过程中,虚表指针实际上是通过目标类确定的。

比如讲a赋值到B的一个实例b,b还是B的虚表。


多继承:

源码如下:

class X{
int i;
virtual void foo(){
  cout<< "X foo";
}
};
class C: public X{
int i;
virtual void foo(){
  cout<< "C foo";
}
};
class B : public X{
public:
virtual void foo(){
  cout<< "B foo";
};
int i;
};
class A:public C, public B{
 public:
 void foo(){
   cout << "A foo";
 }
 int i;
};

(gdb) p a
$1 = {<C> = {<X> = {_vptr.X = 0x400cf0 <vtable for A+16>, i = 4197360}, i = 0}, <B> = {<X> = {_vptr.X = 0x400d08 <vtable for A+40>, i = 4196576}, i = 0}, i = -6976}


虚拟继承:

源码:

using namespace std;
class X{
int i;
virtual void foo(){
  cout<< "X foo";
}
};
class C: public X{
int i;
virtual void foo(){
  cout<< "C foo";
}
};
class B : public X{
public:
virtual void foo(){
  cout<< "B foo";
};
int i;
};
class A:virtual public C, virtual public B{
 public:
 void foo(){
   cout << "A foo";
 }
 int i;
};


int main(void){
  A a;
  cout<<&a;
  a.foo();


  X x;
  B b;
  C c;
}

(gdb) p a
$1 = {<C> = {<X> = {_vptr.X = 0x400d60 <vtable for A+64>, i = 65535}, i = 1}, <B> = {<X> = {_vptr.X = 0x400d80 <vtable for A+96>, i = 4197080}, i = 0}, 
  _vptr.A = 0x400d40 <vtable for A+32>, i = 4197059}
(gdb) p x
$2 = {_vptr.X = 0x400e00 <vtable for X+16>, i = 4196576}
(gdb) p b
$3 = {<X> = {_vptr.X = 0x400dc0 <vtable for B+16>, i = 4197424}, i = 0}
(gdb) p c
$4 = {<X> = {_vptr.X = 0x400de0 <vtable for C+16>, i = 4197517}, i = 0}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值