C++类对象中虚函数表指针、虚函数表、虚函数之间的关系以及在内存中的布局_虚函数表指针 类 对象_子木呀的博客-程序员秘密

技术标签: c#  C++  c++  C/C++、嵌入式知识整理  c语言  C/C++知识点汇总  

目录

C++类的虚函数表和虚函数在内存中的位置

 虚函数表和虚函数在内存中的位置说明

结论


原文 C++类的虚函数表和虚函数在内存中的位置_JMW1407的博客-程序员秘密

C++类的虚函数表和虚函数在内存中的位置

关系:虚函数表指针(保存在堆或栈)->虚函数表(常量区 .rodata)->虚函数(代码段 .text)

虚函数表指针是虚函数表所在位置的地址。虚函数表指针属于对象实例。因而通过new 出来的对象的虚函数表指针位于堆,声名对象的虚函数表指针位于栈。

总结:

1.虚函数表指针位置取决于对象在哪。如果是new的对象,则存在堆上,如果是直接声明,则存在栈上。

2.虚函数表位于只读数据段(.rodata),即:C++内存模型中的常量区;

3.虚函数代码则位于代码段(.text),也就是C++内存模型中的代码区

 
虚函数表和虚函数在内存中的位置说明

 先创建一个有虚函数的类A

class A
{
public:
    A(){};
    ~A(){};
    virtual void vfun(){cout<<"vfun called!"<<endl;}
};

既然要知道虚函数表的位置,那么自然就需要找到虚函数表的地址。我们知道,对于类A这种简单的类,其对象内存布局的最开始四个字节就是一个虚函数表指针(32位编译器),而这个指针变量的值自然就是虚函数表的地址了,因此,第一步就是获取这个虚函数表指针来找到虚函数表的地址

A *a = new A();

long vbaddr=*(int *)a;   //虚函数表地址

由于只有一个虚函数,所以虚函数表的前4个字节肯定就是vfun的函数地址,因此根据虚函数表的地址还可以得到虚函数vfun的地址:

long vfaddr= *(int *)vbaddr;  //虚函数A::vfun的地址

然后还可以根据vfun的地址来调用这个函数:

((void(*)(void))vfaddr)();  //根据得到的地址来调用虚函数 

如果通过vfaddr来调用函数是成功的,那么就说明前面虚函数表的地址都是正确的。

得出以下程序:

#include <iostream>
 
using namespace std;
 
class A
{
public:
    A(){};
    ~A(){};
    virtual void vfun(){cout<<"vfun called!"<<endl;}
};
 
int main()
{
    A *a = new A();
    long vbaddr=*(int *)a;   //虚函数表地址
    long vfaddr= *(int *)vbaddr;   //虚函数vfun地址
    cout<<"addr of vb : "<<vbaddr<<endl;
    cout<<"addr of vfun : "<<vfaddr<<endl;
    
    ((void(*)(void))vfaddr)();   //根据虚函数地址调用虚函数
 
    delete a;
    return 0;
}

用g++进行编译生成可执行文件,然后运行:

从运行结果可以看到,虚函数表的地址是0x400be0(4197344),虚函数vfun的地址为0x400aea(4197098),并且根据虚函数vfun的地址成功调用了虚函数,打印了“vfun called”,这说明获取的0x400be0确实是虚函数表的地址。

接下来就看看0x400be0这个地址在可执行文件内存中的哪个段。

用objdump -s 可以解析ELF格式的可执行文件中的分段信息:

每个分段的内容用Contents of section .xxx来分隔,xxx表示下面的内容属于哪个段。在这些段的内容中,每一行的第一个16进制数表示的是相应的段中的一个地址,以400238 2f6c6962 3634…这一行为例,首地址为0x400238,那么从0x400238到下一行首地址0x400248之间的16个字节中存放的数据就是0x2f 0x6c 0x69 0x62…

回到虚函数表的地址上来,前面说了,虚函数表的地址为0x400be0,现在来看看这个地址是属于哪个段:

可以看到,0x400be0这个地址,刚好就在.rodata这个段中,这个段就是C++中的常量区,并且还可以发现,从这个地址开始取4个字节“ea0a4000”,由于是小端模式,因此取出来的4字节数为0x400aea,是不是很眼熟呢?没错,这个地址就是前面求得的虚函数vfun的地址。

同理,根据虚函数vfun的0x400aea地址,还可以找到虚函数vfun的位置:

 可以看到,虚函数vfun位于.text代码段,也就是C++中的代码区。

结论

综上所述: 虚函数表指针区别于创建的对象是new还是直接声明而放于堆或栈上。C++中虚函数表位于只读数据段(.rodata),也就是C++内存模型中的常量区;而虚函数则位于代码段(.text),也就是C++内存模型中的代码区。

参考
1、https://blog.twofei.com/496/
2、https://blog.csdn.net/Lily_zhangrongli/article/details/106650195
3、https://blog.csdn.net/qq_28114615/article/details/98041319

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41687938/article/details/120479126

智能推荐

工作记录------IdType.ASSIGN_UUID失效问题_诗与猿方的博客-程序员秘密

TableId(value = “id”, type = IdType.ASSIGN_UUID)时myabtispuls的注解,用于给主键id生成一个UUID。这次的BUG,存粹是因为自己对框架理解的太肤浅造成。背景:前端传来的DTO参数,与数据库落表字段并不完全一致。因此自己实现了一个Dto,在保存数据落库时,报错。异常信息:大致内容就是id不能为空,我落表时候传的时空值。

tp依赖的自动注入_tp类自动注入 new 时需要传入_飞马攻城师的博客-程序员秘密

自动注入容器的更多使用主要用于依赖注入,和5.0自动注入的方式有所区别,类的绑定操作不再使用Request 对象而是直接注册到容器中,并且支持模型事件和数据库事件的依赖注入,依赖注入会首先检查容器中是否注册过该对象实例,如果有的话就会自动注入...

Linux下x86_64进程地址空间布局_x86 64 物理地址空间_swartz_lubel的博客-程序员秘密

在x86_64下和i386下是类似的,本文主要关注vm.legacy_va_layout以及kernel.randomize_va_space参数影响下的进程空间内存宏观布局,以及vDSO和多线程下的堆和栈分布。情形一:vm_legacy_va_layout=1kernel.randomize_va_space=0此种情况下采用传统内存布局方式,不开启随机化,程序的内存布局可以看出: 代码段:0x...

英飞凌XMC2GO控制读写英飞凌BGT24LTR22射频板寄存器_y2n1314的博客-程序员秘密

BGT24LTR22是英飞凌推出的一款雷达射频芯片,用它可以产生24Ghz的雷达信号。这是BGT24LTR22的评估版,本篇文章旨在使用XMS2Go与BGT24LTR22芯片进行SPI通信,以达到控制射频板的目的。首先使用Dave软件进行编程#define TICKS_PER_SECOND 1000#define TICKS_WAIT 1000uint8_t i=0;const uint8_t data[9] = {0x01,0x00,0x63,0x03,0x03,0xFD,0x0

编译原理 —— 什么是编译_编译原理什么是编译_starter_zheng的博客-程序员秘密

计算机是如何工作的机器语言:机器语言直接用二进制代码指令表达的计算机语言,指令是用0和1组成的一串代码,可以被计算机直接理解,如上图的C706 0000 0002(以16进制书写,但在计算机中是二进制)汇编语言:汇编语言是面向机器的程序设计语言,使用助记符(Memoni)代替操作码,用地址符号(Symbol)或标号(Label)代替地址码。如上图的MOV X,2高级语言:高级语言是高度封...

【JVM学习-6.1】垃圾回收-- 概述_计忆芳华的博客-程序员秘密

文章目录1. 什么是垃圾1.1 大厂面试题1.2 什么是垃圾( Garbage) 呢?1.3 垃圾收集2. 为什么需要GC3. 早期垃圾回收4. Java垃圾回收机制1. 什么是垃圾Java = (C++)–1.1 大厂面试题蚂蚁金服:你知道哪几种垃圾回收器,各自的优缺点,重点讲一下 cms和g1一面: JVM GC算法有哪些,目前的JDK版本采用什么回收算法一面: ( G1回收器讲下回收过程GC是什么?为什么要有GC?一面: GC的两种判定方法? CMS收集器与G1收集器的特点。百

随便推点

linux服务器上yum安装指定版本php_linux安装php指定版本_7*24 工作者的博客-程序员秘密

在很多情况下安装php是,如果通过yum安装,会导致安装的php版本比较低,如果在开发的时候用的是高版本的php,这个时候我们在服务器上安装php必须使用源码编译安装,如果在编译的时候报错了,可能需要很长的时间排错,然后重新编译安装,这样花费的时间比较长。使用yum安装指定版本的php1、下载对应的仓库## centos7 版本yum install -y https://mirrors.tuna.tsinghua.edu.cn/remi/enterprise/remi-release-7.rpm

react-native android状态栏_rn 混合开发 修改状态栏颜色_barnett_y的博客-程序员秘密

react-native 开发App的时候难免会遇到状态,栏的,背景颜色和字体颜色与App内容页面,色调适配,间言之就是将状态栏颜色与App颜色一致,使用户界面更加整体。1.android设备系统元素导航栏:就是设备顶部的网络、时间、电量等信息栏ActionBar: 返回按钮以及系统默认的header区域,RN开发中一般不会用到,RN中在navigation中进行定制导航栏: 设备下方的物理返回、...

java基础面试_weixin_34198797的博客-程序员秘密

* 以下内容是我在准备java面试的时候觉得有用,面试官很可能会问的一些问题* 内容(除了代码)详情来自网络(学习的时候遇到不会的百度的 (*^__^*) )* 如果大家发现有什么地方不对,请告诉我。谢啦!!☆⌒(*^-゜)v1:java的基础类型Java语言提供了八种基本语言booleanchar byte 8位...

最小生成树之prim算法_北冥有鱼555的博客-程序员秘密

最小生成树之prim算法  最小生成树的概念在上一篇文章中已经叙述了,在这里主要再叙述一种解决最小生成树的算法--prim算法。  他其实和kruskal算法是互补的,一个从边出发,另一个从点出发寻找最小的总权值。prim算法是从点入手,一开始随机选一个点,找出该点连接的其他所有点中最短的路径,然后把这条边的另一个点加入集合。当集合中的点大于等于2的时候,我们每次都要找出这个集合中与其他不

学了 Python 能用来做这些!_anhan9016的博客-程序员秘密

来源商业新知网,原标题:学了 Python 能用来做什么?说起编程语言,Python 也许不是使用最广的,但一定是现在被谈论最多的。随着近年大数据、人工智能的兴起,Python 越来越多的出现在人们的视野中。那么人们在谈论 Python 的时候究竟在谈论什么?Python 的实际应用场景有哪些?这里给大家简单做一个介绍:Web 应用开发在因大数据、人工智能为人所熟知之...

python勾股定理、0-30_Python Tip(31~35)题详解_weixin_39675728的博客-程序员秘密

题目31:山峰的个数描述:十一假期,小P出去爬山,爬山的过程中每隔10米他都会记录当前点的海拔高度(以一个浮点数表示),这些值序列保存在一个由浮点数组成的列表h中。回到家中,小P想研究一下自己经过了几个山峰,请你帮他计算一下,输出结果。例如:h=[0.9,1.2,1.22,1.1,1.6,0.99], 将这些高度顺序连线,会发现有两个山峰,故输出一个2(序列两端不算山峰)答案:count = 0f...

推荐文章

热门文章

相关标签