C++ 内存分析

news/2024/7/4 0:48:31 标签: c++, class, alignment, 编译器, c, 语言
cle class="tags" href="/tags/CLASS.html" title=class>class="baidu_pl">
cle_content" class="tags" href="/tags/CLASS.html" title=class>class="article_content clearfix">
content_views" class="tags" href="/tags/CLASS.html" title=class>class="htmledit_views">

转自 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

为什么会出现这种结果呢?初学者肯定会很烦恼是吗?类a࿰c;b明明是空类࿰c;它的大小应该为为0࿰c;为什么 class="tags" href="/tags/BianYiQi.html" title=编译器>编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化)࿰c;每个实例在内存中都有一个独一无二的地址࿰c;为了达到这个目的࿰c;class="tags" href="/tags/BianYiQi.html" title=编译器>编译器往往会给一个空类隐含的加一个字节࿰c;这样空类在实例化后在内存得到了独一无二的地址.所以a࿰c;b的大小为1.

而类c是由类a派生而来࿰c;它里面有一个纯虚函数࿰c;由于有虚函数的原因࿰c;有一个指向虚函数的指针(vptr)࿰c;在32位的系统分配给指针的大小为4个字节࿰c;所以最后得到c类的大小为4.

类d的大小更让初学者疑惑吧࿰c;类d是由类b࿰c;c派生迩来的࿰c;它的大小应该为二者之和5࿰c;为什么却是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;类的静态数据成员是一种特殊的全局变量.

所以a࿰c;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 A࿰c;由于只有一个虚函数࿰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 B࿰c;由于class="tags" href="/tags/CLASS.html" title=class>class B虚基础了class="tags" href="/tags/CLASS.html" title=class>class A࿰c;同时还拥有自己的虚函数。那么class="tags" href="/tags/CLASS.html" title=class>class B中首先拥有一个vfptr_B࿰c;指向自己的虚函数表。还有char j[3]࿰c;做一次class="tags" href="/tags/ALIGNMENT.html" title=alignment>alignment࿰c;一般大小为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_C࿰c;然后是char i[3]࿰c;然后是vbptr_C_B࿰c;然后是class="tags" href="/tags/CLASS.html" title=class>class B࿰c;所以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_B࿰c;为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class="tags" href="/tags/CLASS.html" title=class>class B的虚类表。看看VB table࿰c;VB table有两项࿰c;第一项为FFFFFFFC࿰c;这一项的值可能没啥意义࿰c;可能是为了保证虚类表不为空吧。第二项为8࿰c;看起来像是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; 谢谢原作者。


cle>

http://www.niftyadmin.cn/n/1436315.html

相关文章

开始我的扫盲之旅

一直想做点什么&#xff0c;但发现自己能力上还是有好多不足&#xff0c;甚至感觉自己算不上是一个合格的程序员。 自己应该要做点什么了。不能在那乱想乱猜。 从哪里做起呢&#xff1f;实践是检验一切的真理 。好吧&#xff0c;我的扫盲一路从现在出发。 自己知识的储备决定了…

扫自己只盲 1

天下文章一大抄。 虽然我的是抄的的文章&#xff0c;但我不是为了别的&#xff0c; 只是想让我坚持的写下去。 地址 引用 和指针&#xff1a; 地址&#xff1a; 在c 中&#xff0c; 地址标号使用十六进制表示[我感觉无所谓 全是二进 X[补] &#xff08;KX&#xff09;…

学习日记杂谈: 网络浏览服务:应用netdrive

最近工作需要&#xff0c;学习脚本简化工作中的操作流程&#xff0c;有需求要从ftp 中实现搜索特定文件 下载到本地。 怎么在网络端进行搜索特定文件问题&#xff0c;一直没有解决。最后通过变通的方法&#xff0c;通过手动操作把网络资源映射成本地磁盘&#xff0c;通过脚本进…

VBS短信飞信发送类(VBSFetion)

VBS短信飞信发送类&#xff08;VBSFetion&#xff09; 标签&#xff1a; Fetion, VB, VBS, VBScript, VBSFetion, 短信, 飞信 标题: VBS短信飞信发送类&#xff08;VBSFetion&#xff09; 作者: Demon 链接: http://demon.tw/my-work/vbsfetion.html 版权: 本博客的所有文章&am…

Esp 和 Ebp

一直对寄存器ESP和EBP的概念总是有些混淆&#xff0c;查看定义ESP是栈顶指针&#xff0c;EBP是存取堆栈指针。还是不能很透彻理解。之后借于一段汇编代码&#xff0c;总算是对两者有个比较清晰的理解。下面是按调用约定__stdcall 调用函数test(int p1,int p2)的汇编代码;假设执…

Windows修改注册表按键映射

Scancode Map 注册表项来实现键盘按键映射。 位于注册表的&#xff1a; HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout\ 下新建一个二进制值项&#xff1a; "Scancode Map" 这个项目可实现对键盘按键的映射。这里映射的意思可理解为“替换…

yolov 论文发表在什么期刊上_技师论文发表【期刊论文】技师论文发表

怎样写论文&#xff1f;要掌握写作的基本要求。不言而喻&#xff0c;许多作者都有相关的论文写作经验。基本要求的把握应该不是问题。写论文是为了发表。如何使自己的文章脱颖而出&#xff0c;顺利发表&#xff0c;是作者的共同愿望&#xff0c;这不仅需要把握写作的基本要求&a…

jquery video全屏_请问,怎么用JQuery监听html5 中video 标签的全屏按钮?

展开全部这个可以检测 videoWidth 和 videoHeight 这两个属性是否等于自己创建的的属性&#xff0c;不等于则是全屏。jQuery是一套跨32313133353236313431303231363533e59b9ee7ad9431333365646235浏览器的JavaScript库&#xff0c;简化HTML与JavaScript之间的操作。由John Resi…