转自 http://blog.csdn.net/link_/article/details/7328740
以下的讨论都是在不用#pragma pack( num )下的结果c;num表示以多少个字节对齐。
初学者在学习面向对象的程序设计语言时c;或多或少的都些疑问c;我们写的代码与最终生编译成的代码却 大相径庭c;我们并不知道class="tags" href="/tags/BianYiQi.html" title=编译器>编译器在后台做了什么工作.这些都是由于我们仅停留在语言层的原因c;所谓语言层就是教会我们一些基本的语法法则c;但不会告诉我们为什么这么做?今天和大家谈的一点感悟就是我在学习编程过程中的一点经验c;是class="tags" href="/tags/BianYiQi.html" title=编译器>编译器这方面的一个具体功能.
首先:我们要知道什么是类的实例化c;所谓类的实例化就是在内存中分配一块地址.
那我们先看看一个例子:
#include<iostream.h>
class="tags" href="/tags/CLASS.html" title=class>class a {};
class="tags" href="/tags/CLASS.html" title=class>class b{};
class="tags" href="/tags/CLASS.html" title=class>class c:public a{
virtual void fun()=0;
};
class="tags" href="/tags/CLASS.html" title=class>class d:public b,public c{};
int main()
{
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
return 0;}
程序执行的输出结果为:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
上面是在VC++6.0编译的结果c;但是在Dev-C++和Code::Blocks下得出的结果是 sizeof( d ) = 4
为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类ac;b明明是空类c;它的大小应该为为0c;为什么 class="tags" href="/tags/BianYiQi.html" title=编译器>编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化)c;每个实例在内存中都有一个独一无二的地址c;为了达到这个目的c;class="tags" href="/tags/BianYiQi.html" title=编译器>编译器往往会给一个空类隐含的加一个字节c;这样空类在实例化后在内存得到了独一无二的地址.所以ac;b的大小为1.
而类c是由类a派生而来c;它里面有一个纯虚函数c;由于有虚函数的原因c;有一个指向虚函数的指针(vptr)c;在32位的系统分配给指针的大小为4个字节c;所以最后得到c类的大小为4.
类d的大小更让初学者疑惑吧c;类d是由类bc;c派生迩来的c;它的大小应该为二者之和5c;为什么却是8 呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则c;里哪个最近的倍数c;就是该类的大小c;所以类d的大小为8个字节.
当然在不同的class="tags" href="/tags/BianYiQi.html" title=编译器>编译器上得到的结果可能不同c;但是这个实验告诉我们初学者c;不管类是否为空类c;均可被实例化(空类也可被实例化)c;每个被实例都有一个独一无二的地址.
我所用的class="tags" href="/tags/BianYiQi.html" title=编译器>编译器为vc++ 6.0.
下面我们再看一个例子.
#include<iostream.h>
class="tags" href="/tags/CLASS.html" title=class>class a{
pivate:
int data;
};
class="tags" href="/tags/CLASS.html" title=class>class b{
private:
int data;
static int data1;
};
int b::data1=0;
void mian(){
cout<<"sizeof(a)="<<sizeof(a)<<endl;
cout<<"sizeof(b)="<<sizeof(b)<<endl;
}
执行结果为:
sizeof(a)=4;
sizeof(b)=4;
为什么类b多了一个数据成员c;却大小和类a的大小相同呢?因为:类b的静态数据成员被class="tags" href="/tags/BianYiQi.html" title=编译器>编译器放在程序的一个global data members中c;它是类的一个数据成员.但是它不影响类的大小c;不管这个类实际产生 了多少实例c;还是派生了多少新的类c;静态成员数据在类中永远只有一个实体存在c;而类的非静态数据成员只有被实例化的时候c;他们才存在.但是类的静态数据成员一旦被声明c;无论类是否被实例化c;它都已存在.可以这么说c;类的静态数据成员是一种特殊的全局变量.
所以ac;b的大小相同.
下面我们看一个有构造函数c;和析构函数的类的大小c;它又是多大呢?
#include<iostream.h>
class="tags" href="/tags/CLASS.html" title=class>class A{
public :
A(int a){
x=a;}
void f(int x){
cout<<x<<endl;}
~A(){}
private:
int x;
int g;
};
class="tags" href="/tags/CLASS.html" title=class>class B{
public:
private:
int data; int data2;
static int xs;
};
int B::xs=0;
void main(){
A s(10);
s.f(10);
cout<<"sozeof(a)"<<sizeof(A)<<endl;
cout<<"sizeof(b)"<<sizeof(B)<<endl;
}程序执行输出结果为:
10 ,
sizeof(a) 8
sizeof(b) 8
它们的结果均相同c;可以看出类的大小与它当中的构造函数c;析构函数c;以及其他的成员函数无关c;只与它当中的成员数据有关.
从以上的几个例子不难发现类的大小:
1.为类的非静态成员数据的类型大小之和.
2.有class="tags" href="/tags/BianYiQi.html" title=编译器>编译器额外加入的成员变量的大小c;用来支持语言的某些特性(如:指向虚函数的指针).
3.为了优化存取效率c;进行的边缘调整.
4 与类中的构造函数c;析构函数以及其他的成员函数无关.
color:#0000ff; font-size:14pt">虚继承之单继承的内存布局
先看一段代码
color:#0000ff">class="tags" href="/tags/CLASS.html" title=class>class A
{
virtual aa(){};
};
color:#0000ff; font-size:14pt">class="tags" href="/tags/CLASS.html" title=class>class B : public virtual A
color:#0000ff; font-size:14pt">{
color:#0000ff; font-size:14pt"> char j[3]; //加入一个变量是为了看清楚class="tags" href="/tags/CLASS.html" title=class>class中的vfptr放在什么位置
color:#0000ff; font-size:14pt"> public:
color:#0000ff; font-size:14pt"> virtual bb(){};
color:#0000ff; font-size:14pt">};
color:#0000ff; font-size:14pt">class="tags" href="/tags/CLASS.html" title=class>class C : public virtual B
color:#0000ff; font-size:14pt">{
color:#0000ff; font-size:14pt"> char i[3];
color:#0000ff; font-size:14pt"> public:
color:#0000ff; font-size:14pt"> virtual cc(){};
color:#0000ff; font-size:14pt">};
color:#000000; font-size:14pt">这次先不给结果c;先分析一下c;也好加强一下印象。
color:#000000; font-size:14pt">1、对于class="tags" href="/tags/CLASS.html" title=class>class Ac;由于只有一个虚函数c;那么必须得有一个对应的虚函数表c;来记录对应的函数入口地址。同时在class="tags" href="/tags/CLASS.html" title=class>class A的内存空间中之需要有个vfptr_A指向该表。sizeof(A)也很容易确定c;为4。
color:#000000; font-size:14pt">2、对于class="tags" href="/tags/CLASS.html" title=class>class Bc;由于class="tags" href="/tags/CLASS.html" title=class>class B虚基础了class="tags" href="/tags/CLASS.html" title=class>class Ac;同时还拥有自己的虚函数。那么class="tags" href="/tags/CLASS.html" title=class>class B中首先拥有一个vfptr_Bc;指向自己的虚函数表。还有char j[3]c;做一次class="tags" href="/tags/ALIGNMENT.html" title=alignment>alignmentc;一般大小为4。可虚继承该如何实现咧?首先要通过加入一个虚l类指针(记vbptr_B_A)来指向其父类c;然后还要包含父类的所有内容。有些复杂了c;不过还不难想象。sizeof(B)= 4+4+4+4=16(vfptr_B、char j[3]做class="tags" href="/tags/ALIGNMENT.html" title=alignment>alignment、vbptr_B_A和class="tags" href="/tags/CLASS.html" title=class>class A)。
color:#000000; font-size:14pt">3、在接着是class="tags" href="/tags/CLASS.html" title=class>class C了。class="tags" href="/tags/CLASS.html" title=class>class C首先也得有个vfptr_Cc;然后是char i[3]c;然后是vbptr_C_Bc;然后是class="tags" href="/tags/CLASS.html" title=class>class Bc;所以sizeof(C)=4+4+4+16=28(vfptr_C、char i[3]做class="tags" href="/tags/ALIGNMENT.html" title=alignment>alignment、vbptr_C_A和class="tags" href="/tags/CLASS.html" title=class>class B)。
在VC 6.0下写了个程序c;把上面几个类的大小打印出来c;果然结果为4、16、28。
VC中虚继承的内存布局——单继承
class="tags" href="/tags/CLASS.html" title=class>class A的情况太简单c;没问题。从class="tags" href="/tags/CLASS.html" title=class>class B的内存布局图可以得出下面的结论。
1、vf_ptr B放在了类的首部c;那么如果要想直接拿memcpy完成类的复制是很危险的c;用struct也是不行的。
2、vbtbl_ptr_Bc;为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class="tags" href="/tags/CLASS.html" title=class>class B的虚类表。看看VB tablec;VB table有两项c;第一项为FFFFFFFCc;这一项的值可能没啥意义c;可能是为了保证虚类表不为空吧。第二项为8c;看起来像是class="tags" href="/tags/CLASS.html" title=class>class B中的class="tags" href="/tags/CLASS.html" title=class>class A相对该vbtbl_ptr_B的位移c;也就是一个offset。类似的方法在C++ Object Model(P121)有介绍c;可以去看看。
class="tags" href="/tags/CLASS.html" title=class>class C的内存布局就比较复杂了c;不过它的内存布局也更一步说明我对vbtbl_ptr_B中的内容c;也就是虚类表的理解是正确的。不过值得关注的是class="tags" href="/tags/CLASS.html" title=class>class B中的class="tags" href="/tags/CLASS.html" title=class>class A在布局时被移到前面去了c;虽然整个大小没变c;但这样一来如果做这样的操作 C c; B *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class="tags" href="/tags/CLASS.html" title=class>class B的位置既可赋值给b。但是在构建class="tags" href="/tags/CLASS.html" title=class>class C时会复杂一些c;后面的使用还是非常简单的c;效率也比较高。class="tags" href="/tags/CLASS.html" title=class>class A的内存布局被前移可能是考虑倒C的虚继承顺序吧。
结论
color:#ff0000">1、VC在编译时会把vfptr放到类的头部;
2、VC采用虚表指针(vbtbl_ptr)来确定某个类所继承的虚类。
3、VC会重新调整虚继承的父类在子类中内存布局。(具体规则还不清楚)
4、VC中虚类表中的第一项是无意义的c;可能是为了保证sizeof(虚类表)!=0;后面的内容为父类在子类中相对该虚类表指针的偏移量。
color:#ff0000">之前有些了解c; 看完之后更清晰了c; 谢谢原作者。