[GeekBand] C++继承关系下虚函数内存分布
本文参考文献:GeekBand课堂内容,授课老师:侯捷
:深度探索C++对象模型(侯捷译)
:网络资料,如:http://blog.csdn.net/sanfengshou/article/details/4574604
说明:由于条件限制,仅测试了Windows平台下的VS2013 IDE。其余平台结果可能不同,但原理都类似。建议读者自己在其他平台进行测试。
1、什么是虚函数?
虚函数是类的非静态成员函数,在类中的基本形式如下:virtual 函数返回值类型 虚函数名(形参表)
如:virtual void process()
2、虚函数的作用,为什么采用虚函数?
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数,在定义了虚函数后,可以在基类的派生类中对虚函数重新定义(形式也是:virtual 函数返回值类型 虚函数名(形参表){ 函数体 }),在派生类中重新定义的函数应与虚函数具有相同的形参个数和形参类型。以实现统一的接口,不同定义过程。如果在派生类中没有对虚函数重新定义,则它继承其基类的虚函数。
3、正文
首先,假设有一个Fruit类,如下所示:
//基类
class Fruit
{
public:
//构造函数
Fruit(const int no_,const double weight_,const char key_) :no(no_), weight(weight_), key(key_){};
//打印变量内存地址
void print()
{
cout << "no :" << no << " | " << " Memory Address no: " << (void*)&no << endl;
cout << "weight :" << weight << " | " << " Memory Address weight: " << (void*)&weight << endl;
cout << "key :" << key << " | " << " Memory Address key: " << (void*)&key << endl;
}
//虚函数的影响
virtual void process()
{
cout << "the Process function of Base Fruit is called!" << endl;
}
private:
int no;
double weight;
char key;
};
我们知道,int类型为4个字节,double类型为8个字节,char类型为1个字节,又知道Class中存在着字节对齐的说法。这里有流传比较广的三原则:
1、偏移地址和成员占用大小均需要对齐;
2、结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)
3、结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的整数倍.不足的要补齐.
由此可以推断出,此时数据段大小应为24字节
如果数据更换位置了呢?比如,感兴趣的朋友自己验证下:
private:
char key;
int no;
double weight;
最后,相信大家都知道 #pragma pack(),这个函数。这里面又有着效率等问题,以后再详细的分析它,由于与此标题无关,就不展开说了。
测试代码如下所示:
#include "stdafx.h"
#include"Object.h"
#include"iostream"
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
//基类(水果):测试数据
cout << "------------------------------------------------------------------" << endl;
cout << "The Memory Test Data of Fruit Class !"<< endl;
cout << "------------------------------------------------------------------" << endl;
Fruit TestFruit(,0.0,'b');
cout << "Fruit Class Size : " << sizeof(Fruit) << endl; //类的大小
cout << "Fruit Memory Layout FirstAddress: " << (void*)&TestFruit << endl; //类的首地址
//虚函数表的地址存在在前四个字节中
cout << "Fruit Virtual Function Table Address:" << (int*)(&TestFruit) << endl; //虚函数表地址
//打印类的信息
TestFruit.print(); //类中成员的地址信息
cout << "Fruit Virtual process Function Address: " << (int*)*(int*)&TestFruit + << endl; //虚函数表中函数process函数首地址
// 通过函数指针调用函数,验证正确性
typedef void(*func_pointer)(void);
func_pointer fptr = NULL;
fptr = (func_pointer)*((int*)*(int*)&TestFruit + ); // v_func1()
fptr(); //process
}
结果如下所示:
根据显示结果,所以我们可以大致的画出内存分布图:
其中
1、Fruit类为类,大小为 sizeof(Fruit) = 32,
2、vptr 为虚指针,指向了虚函数表
3、int类型为4字节,为了对齐,所以填充了4个字节。同理,char填充了7个字节。
如果派生类和基类有共同的虚函数时内存如何分布呢?
子类代码如下,注意此时子类和基类都有virtual void process()函数
#ifndef _OBJECT_H_
#define _OBJECT_H_
#include"iostream"
using namespace std;
//基类
class Fruit
{
public:
//构造函数
Fruit(const int no_,const double weight_,const char key_) :no(no_), weight(weight_), key(key_){};
//打印变量内存地址
void print()
{
cout << "no :" << no << " | " << " Memory Address no: " << (void*)&no << endl;
cout << "weight :" << weight << " | " << " Memory Address weight: " << (void*)&weight << endl;
cout << "key :" << key << " | " << " Memory Address key: " << (void*)&key << endl;
}
//虚函数的影响
virtual void process()
{
cout << "the Process function of Base Fruit is called!" << endl;
}
private:
int no;
double weight;
char key;
}; //派生类
//这里考虑自己本身的虚函数,及基类的虚函数
class Apple : public Fruit
{
public:
//构造函数
Apple(const Fruit& fruit_,const int size_,const char type_) :size(size_), type(type_),Fruit(fruit_){};
//打印成员数据
void save()
{
cout << "size :" << size << " | " << " Apple Memory Address no: " << (void*)&size << endl;
cout << "type :" << type << " | " << " Apple Memory Address weight: " << (void*)&type << endl;
}
virtual void process()
{
cout << "the Process function of Derived Apple is called!" << endl;
}
private:
int size;
char type;
}; #endif
完整测试代码如下:
// TestObjectSize.cpp : 定义控制台应用程序的入口点。
// #include "stdafx.h"
#include"Object.h"
#include"iostream"
using namespace std; int _tmain(int argc, _TCHAR* argv[])
{
//基类(水果):测试数据
cout << "------------------------------------------------------------------" << endl;
cout << "The Memory Test Data of Fruit Class !"<< endl;
cout << "------------------------------------------------------------------" << endl;
Fruit TestFruit(,0.0,'b');
cout << "Fruit Class Size : " << sizeof(Fruit) << endl; //类的大小
cout << "Fruit Memory Layout FirstAddress: " << (void*)&TestFruit << endl; //类的首地址
//虚函数表的地址存在在前四个字节中
cout << "Fruit Virtual Function Table Address:" << (int*)(&TestFruit) << endl; //虚函数表地址
//打印类的信息
TestFruit.print(); //类中成员的地址信息
cout << "Fruit Virtual process Function Address: " << (int*)*(int*)&TestFruit + << endl; //虚函数表中函数process函数首地址
// 通过函数指针调用函数,验证正确性
typedef void(*func_pointer)(void);
func_pointer fptr = NULL;
fptr = (func_pointer)*((int*)*(int*)&TestFruit + ); // v_func1()
fptr(); //process //派生类(苹果):测试数据
cout << "------------------------------------------------------------------" << endl;
cout << "The Memory Test Data of Apple Class !" << endl;
cout << "------------------------------------------------------------------" << endl;
Apple TestApple(TestFruit, , 't');
cout << "Apple Class Size : " << sizeof(Apple) << endl; //类的大小
cout << "Apple Memory Layout FirstAddress: " << (void*)&TestApple << endl; //类的首地址
//虚函数表的地址存在在前四个字节中
cout << "Apple Virtual Function Table Address:" << (int*)(&TestApple) << endl; //虚函数表地址
//查看基类Fruit类的信息
TestApple.print();
//打印Apple类的信息
TestApple.save(); //类中成员的地址信息
cout << "Apple Virtual process Function Address: " << (int*)*(int*)&TestApple + << endl; //虚函数表中函数process函数首地址
// 通过函数指针调用函数,验证正确性
typedef void(*func_pointer2)(void);
func_pointer fptr2 = NULL;
fptr2 = (func_pointer2)*((int*)*(int*)&TestApple + ); // v_func1()
fptr2(); //process system("pause");
return ;
}
我们先看下实际结果:
通过结果,我们可以画出此时子类的内存分布图。
根据显示结果,我们可以进一步分析!
其中
1、Apple类为派生类,大小为 sizeof(Apple) = 40,
2、vptr 为虚指针,指向了虚函数表.同时,先对基类进行了内存分配,然后再对子类进行内存分配。
3、派生类中int类型为4字节,char 为1字节。为了对齐,char 类型填充了3个字节、
根据以上分析:整体框架如图:
4.1、进一步的思考
1、 通过以上分析,我们基本上知道了系统如何进行内存分布的,我们这里对虚函数表内存进一步的观察与思考:
打印出来的信息 : Fruit Virtual process Function Address : 001AEC78
Apple Virtual process Function Address :001AEDB0
观察得到,从内存角度出发,基类的地址(001AEC78)要小于子类(001AEDB0),这说明基类的虚函数在子类的前面。
2、分别在Fruit类、Apple类中添加虚函数:
//测试用,非本题范围
virtual void process_b0()
{
cout << "This is Base Fruit class's process_b0" << endl;
}
及Apple类中添加虚函数
//测试用,非本题范围
virtual void process_b1()
{
cout << "This is Derived Apple_class's process_b1" << endl;
}
3、然后修改测试代码段,将检验一次,改为三次
// 通过函数指针调用函数,验证正确性
//typedef void(*func_pointer2)(void);
//func_pointer fptr2 = NULL;
//fptr2 = (func_pointer2)*((int*)*(int*)&TestApple + 0); // v_func1()
//fptr2(); //process // 通过函数指针调用函数,验证调用问题
typedef void(*func_pointer)(void);
func_pointer fp = NULL;
for (int i = ; i<; i++) {
fp = (func_pointer)*((int*)*(int*)&TestApple + i);
fp();
4、运行后结果如图所示:
通过结果进一步的思考与分析:
1、显示:The Progress function of Deviced Apple is called !同名的process,运行的是子类的process()。说明此时用子类的虚函数process()代替了父类的虚函数process()!
2、先显示:this is Base Fruit class proccess_b0 ,后显示:This is Deviced Apple class process_b1.进一步说明了基类的虚函数内存分布在子类的之前!
基本关系如图所示:
4.2、调试技巧
在VS2013 DE中有很多方便的工具,可以清晰的观察出变量等各种信息,如图通过debug模式下即可查看出很多关键信息!
2、通过反汇编观察,这点还不是很熟,不过以后要加强调试经验。
以上就是我个人的一些不成熟的学习笔记,望各位批评指正。谢
[GeekBand] C++继承关系下虚函数内存分布的更多相关文章
- C++类虚函数内存分布(这个 你必须懂)
转自:http://www.cnblogs.com/jerry19880126/p/3616999.html C++类内存分布 书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来 ...
- 谈谈c++中继承中的虚函数
c++继承中的虚函数 c++是一种面向对象的编程语言的一个很明显的体现就是对继承机制的支持,c++中继承分很多种,按不同的分类有不同分类方法,比如可以按照基类的个数分为多继承和单继承,可以按照访问 ...
- Jaskson精讲第7篇-类继承关系下的JSON序列化与反序列化JsonTypeInfo
Jackson是Spring Boot(SpringBoot)默认的JSON数据处理框架,但是其并不依赖于任何的Spring 库.有的小伙伴以为Jackson只能在Spring框架内使用,其实不是的, ...
- 继承关系下的this关键字
继承关系下的this关键字 在继承关系下,父类中的this关键字并不总是表示父类中的变量和方法.this关键字的四种用法如前文所述,列举如下. 1) this(paras…); 访问其他的构造方法 2 ...
- C++学习 之 类的继承中的虚函数(笔记)
1.多态行为 多态是面向对象语言的一种特征,让我们能够以类似的方式处理不同类型的对象.在C++中我们可以通过继承层次结构实现子类型多态. 我们可以通过下面的代码进一步了解多态: #include< ...
- C++ 子类继承父类纯虚函数、虚函数和普通函数的区别
C++三大特性:封装.继承.多态,今天给大家好好说说继承的奥妙 1.虚函数: C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现.子类可以重写父类的虚函数实现子类 ...
- C++继承-重载-多态-虚函数
C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...
- c++继承关系中成员函数的重载、重写、重定义之间的区别
1.Override.Overload.Redefine Overload 重载只能发生在类内部,不能发生在子类和父类的继承中.具体来说,如果子类中有父类同名.同返回值类型,但是不同参数列表,这两个在 ...
- [c++] C++多态(虚函数和虚继承)
转自:https://www.jianshu.com/p/02183498a2c2 面向对象的三大特性是封装.继承和多态.多态是非常重要的一个特性,C++多态基于虚函数和虚继承实现,本文将完整挖掘C+ ...
随机推荐
- kindle paperwhite 简单笔记按名称分类
已更新python,见新博客 http://www.hrwhisper.me/archives/708 写作背景: 南京决赛比赛完那天晚上写的. 使用方法: 将My Clippings.txt 放在 ...
- session的生命周期是怎样的
session的生命周期是怎样的 一.总结 一句话总结:Tomcat中Session的默认失效时间为20分钟.如果我们敲代码的时候把它设置成1个月,那么这一个月的数据会代替默认20分钟的数据,使ses ...
- GO语言学习(四)GO语言语言结构
Go Hello World 实例 Go 语言的基础组成有以下几个部分: 包声明 引入包 函数 变量 语句 & 表达式 注释 接下来让我们来看下简单的代码,该代码输出了"Hello ...
- [Angular] Setup automated deployment with Angular, Travis and Firebase
Automate all the things!! Automation is crucial for increasing the quality and productivity. In this ...
- C++——多态性实现机制
C++的多态性实现机制剖析 1. 多态性和虚函数 #include <iostream.h> class animal { public: void sleep() { cout<& ...
- Instruction-Set Support for Invocation of VMM-Configured Services without VMM Intervention
A processing core comprising instruction execution logic circuitry and register space. The register ...
- ORACLE表空间的备份与恢复策略
转自原文如何进行ORACLE表空间的备份与恢复? 1.切换服务器归档模式,如果已经是归档模式可跳过此步: %sqlplus /nolog (启动sqlplus) SQL> conn / as s ...
- php实现兼容Unicode文字的字符串大写和小写转换strtolower()和strtoupper()
前言 网上流传着这么一个腾讯笔试题: PHP的strtolower()和strtoupper()函数在安装非中文系统的server下可能会导致将汉字转换为乱码,请写两个替代的函数实现兼容Unicode ...
- Visual Studio中你所不知道的智能感知
在Visual Studio中的智能感知,相信大家都用过.summary,param,returns这几个相信很多人都用过的吧.那么field,value等等这些呢. 首先在Visual Studio ...
- KMP小结
1. KMP模版: 代表题目:POJ 3641 Oulipo KMP http://blog.csdn.net/murmured/article/details/12871891 char P[MAX ...