C++ 内存布局 - Part1: typeid, typeinfo及单继承

news2024/11/13 9:13:46

1. typeinfo定义

typeinfo中存储的是关于类型的信息,可以通过typeid操作符获取,对于没有虚函数的场景,typeid返回的是编译器静态类型信息,对于一个基类类型指针,哪怕其真实指向是个派生类,如果没有虚函数,那么typeid返回的也还是基类类型,而对于有虚函数的类来说,由于虚表的存在,可以在运行期动态判断类型。

以下代码都是在X86_64 Linux中完成,g++编译。

2. 没有虚函数的类继承

示例代码:

#include <iostream>
#include <typeinfo>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int b, int d) : Base(b), derivedValue(d) {}
};

int main() {
    Base b(10);          // 基类对象
    Derived d(20, 30);   // 派生类对象
    Base* ptr = &d;      // 基类指针指向派生类对象

    // 打印类型信息
    std::cout << "Type of b: " << typeid(b).name() << std::endl;          // 输出: Base
    std::cout << "Type of d: " << typeid(d).name() << std::endl;          // 输出: Derived
    std::cout << "Type of ptr: " << typeid(*ptr).name() << std::endl;     // 输出: Base

    return 0;
}

运行结果如下:

$ ./vtable_1b
Type of b: 4Base
Type of d: 7Derived
Type of ptr: 4Base

可见,在没有虚函数的情况下,即使指针 ptr实际指向的是派生类对象,但是没有虚表的帮助,只能返回编译器静态类型,也就是基类类型。

3. 重写虚函数的类继承

将上面的代码稍作修改,引入一个虚函数,代码如下:

#include <iostream>
#include <typeinfo>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
    virtual void fool() {std::cout << "hello, I'm Base" << std::endl;}
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int b, int d) : Base(b), derivedValue(d) {}
    virtual void fool() {std::cout << "hello, I'm Derived" << std::endl;}
};

int main() {
    Base b(10);          // 基类对象
    Derived d(20, 30);   // 派生类对象
    Base* ptr = &d;      // 基类指针指向派生类对象

    // 打印类型信息
    std::cout << "Type of b: " << typeid(b).name() << std::endl;          // 输出: Base
    std::cout << "Type of d: " << typeid(d).name() << std::endl;          // 输出: Derived
    std::cout << "Type of ptr: " << typeid(*ptr).name() << std::endl;     // 输出: Base

    b.fool();
    d.fool();
    ptr->fool();

    return 0;
}

运行结果:

$ ./vtable_1c
Type of b: 4Base
Type of d: 7Derived
Type of ptr: 7Derived
hello, I'm Base
hello, I'm Derived
hello, I'm Derived

内存布局

对于上面的虚函数继承例子,gdb打印对象内存:

(gdb) set print asm-demangle on
(gdb) set print demangle on
(gdb) p b
$6 = {_vptr.Base = 0x400cd0 <vtable for Base+16>, baseValue = 10}
(gdb) p d
$7 = {<Base> = {_vptr.Base = 0x400cb8 <vtable for Derived+16>, baseValue = 20}, derivedValue = 30}
(gdb) p sizeof(b)
$8 = 16
(gdb) p sizeof(d)
$9 = 16

对于基类来说,对象里包含一个虚表指针和一个int成员, 考虑到指针的8字节对齐,因此整个对象占用16字节。

对于派生类来说,对象里包含一个虚表指针和两个int成员,同样考虑到指针的8字节对齐,两个int成员共享第二个8字节,因此对象仍然占用16字节。

继续查看虚表:

3.1 基类虚表

从上面的打印可以看出,基类对象的第一个内存成员是vptr, 紧跟着的是成员变量

对象中的vptr指向的是vtable偏移16,因此,vtable的起始地址是0x400cc0

(gdb) x/10xg 0x400cc0
0x400cc0 <vtable for Base>:     0x0000000000000000      0x0000000000400d00
0x400cd0 <vtable for Base+16>:  0x0000000000400b1e      0x0000000000601d98
0x400ce0 <typeinfo for Derived+8>:      0x0000000000400cf0      0x0000000000400d00
0x400cf0 <typeinfo name for Derived>:   0x6465766972654437      0x0000000000000000
0x400d00 <typeinfo for Base>:   0x0000000000601d40      0x0000000000400d10
(gdb) info symbol 0x0000000000400d00
typeinfo for Base in section .rodata of /home/lss/test/vtable_1c
(gdb) info symbol 0x0000000000400b1e
Base::fool() in section .text of /home/lss/test/vtable_1c

vtable + 8 指向的是基类的typeinfo

vtable + 16, 也就是对象中的vptr, 指向基类的虚函数foo()

查看基类typeinfo:

(gdb) x/20xg 0x0000000000400d00
0x400d00 <typeinfo for Base>:   0x0000000000601d40      0x0000000000400d10
0x400d10 <typeinfo name for Base>:      0x0000006573614234      0x000000743b031b01
0x400d20:       0xfffffad80000000d      0xfffffb58000000b8
0x400d30:       0xfffffb8800000090      0xfffffc3e000000a4
0x400d40:       0xfffffd5e00000180      0xfffffd9c000001a4
0x400d50:       0xfffffdb2000001c4      0xfffffde2000000e0
0x400d60:       0xfffffe0600000100      0xfffffe3200000120
0x400d70:       0xfffffe6e00000140      0xfffffea800000160
0x400d80:       0xffffff18000001e8      0x0000000000000230
0x400d90:       0x0000000000000014      0x0110780100527a01
(gdb) info symbol 0x0000000000601d40
vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3 + 16 in section .data.rel.ro of /home/lss/test/vtable_1c
(gdb) info symbol 0x0000000000400d10
typeinfo name for Base in section .rodata of /home/lss/test/vtable_1c
(gdb) x/s 0x0000000000400d10
0x400d10 <typeinfo name for Base>:      "4Base"

图解如下:

3.2 派生类虚表

从之前的打印可以看出,派生类对象的第一个内存成员是vptr, 紧跟着的是基类成员变量,最后是派生类成员变量。

对象中的vptr指向的是vtable偏移16,因此,vtable的起始地址是0x400ca8

(gdb) x/16xg 0x400ca8
0x400ca8 <vtable for Derived>:  0x0000000000000000      0x0000000000400cd8
0x400cb8 <vtable for Derived+16>:       0x0000000000400b86      0x0000000000000000
0x400cc8 <vtable for Base+8>:   0x0000000000400d00      0x0000000000400b1e
0x400cd8 <typeinfo for Derived>:        0x0000000000601d98      0x0000000000400cf0
0x400ce8 <typeinfo for Derived+16>:     0x0000000000400d00      0x6465766972654437
0x400cf8 <typeinfo name for Derived+8>: 0x0000000000000000      0x0000000000601d40
0x400d08 <typeinfo for Base+8>: 0x0000000000400d10      0x0000006573614234
0x400d18:       0x000000743b031b01      0xfffffad80000000d

vtable + 8 指向的是派生类的typeinfo

vtable + 16, 也就是对象中的vptr, 指向派生类的虚函数foo()

查看派生类typeinfo:

Derived::fool() in section .text of /home/lss/test/vtable_1c
(gdb) x/20xg 0x0000000000400cd8
0x400cd8 <typeinfo for Derived>:        0x0000000000601d98      0x0000000000400cf0
0x400ce8 <typeinfo for Derived+16>:     0x0000000000400d00      0x6465766972654437
0x400cf8 <typeinfo name for Derived+8>: 0x0000000000000000      0x0000000000601d40
0x400d08 <typeinfo for Base+8>: 0x0000000000400d10      0x0000006573614234
0x400d18:       0x000000743b031b01      0xfffffad80000000d
0x400d28:       0xfffffb58000000b8      0xfffffb8800000090
0x400d38:       0xfffffc3e000000a4      0xfffffd5e00000180
0x400d48:       0xfffffd9c000001a4      0xfffffdb2000001c4
0x400d58:       0xfffffde2000000e0      0xfffffe0600000100
0x400d68:       0xfffffe3200000120      0xfffffe6e00000140
(gdb) info symbol 0x0000000000601d98
vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3 + 16 in section .data.rel.ro of /home/lss/test/vtable_1c
(gdb) info symbol 0x0000000000400cf0
typeinfo name for Derived in section .rodata of /home/lss/test/vtable_1c
(gdb) info symbol 0x0000000000400d00
typeinfo for Base in section .rodata of /home/lss/test/vtable_1c
(gdb) x/s 0x0000000000400cf0
0x400cf0 <typeinfo name for Derived>:   "7Derived"

图解如下:

由此可见,派生类的typeinfo中包含了指向基类typeinfo的指针。

4. 没有重写虚函数的类继承

如果派生类没有重写基类的虚函数,内存布局会是什么样呢?

修改上面的类:

#include <iostream>
#include <typeinfo>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
    virtual void fool() {std::cout << "hello, I'm Base" << std::endl;}
};

class Derived : public Base {
public:
    int derivedValue;
    Derived(int b, int d) : Base(b), derivedValue(d) {}
};

int main() {
    Base b(10);          // 基类对象
    Derived d(20, 30);   // 派生类对象
    Base* ptr = &d;      // 基类指针指向派生类对象

    // 打印类型信息
    std::cout << "Type of b: " << typeid(b).name() << std::endl;          // 输出: Base
    std::cout << "Type of d: " << typeid(d).name() << std::endl;          // 输出: Derived
    std::cout << "Type of ptr: " << typeid(*ptr).name() << std::endl;     // 输出: Base

    b.fool();
    d.fool();
    ptr->fool();

    return 0;
}

编译后运行:

$ ./vtable_1d
Type of b: 4Base
Type of d: 7Derived
Type of ptr: 7Derived
hello, I'm Base
hello, I'm Base
hello, I'm Base

内存布局

(gdb) set print asm-demangle on
(gdb) set print demangle on
(gdb) p b
$1 = {_vptr.Base = 0x400c88 <vtable for Base+16>, baseValue = 10}
(gdb) p d
$2 = {<Base> = {_vptr.Base = 0x400c70 <vtable for Derived+16>, baseValue = 20}, derivedValue = 30}
(gdb) p sizeof(b)
$3 = 16
(gdb) p sizeof(d)
$4 = 16
(gdb)

下面分析虚表,至于typeinfo的分析和上面类似

4.1 基类虚表

虚表起始地址:0x400c78

(gdb) x/10xg 0x400c78
0x400c78 <vtable for Base>:     0x0000000000000000      0x0000000000400cb8
0x400c88 <vtable for Base+16>:  0x0000000000400b1e      0x0000000000601d98
0x400c98 <typeinfo for Derived+8>:      0x0000000000400ca8      0x0000000000400cb8
0x400ca8 <typeinfo name for Derived>:   0x6465766972654437      0x0000000000000000
0x400cb8 <typeinfo for Base>:   0x0000000000601d40      0x0000000000400cc8
(gdb) info symbol 0x0000000000400b1e
Base::fool() in section .text of /home/lss/test/vtable_1d
(gdb)

foo() 虚函数地址为:0x0000000000400b1e

4.2 派生类虚表

虚表起始地址:0x400c60

(gdb) x/16xg 0x400c60
0x400c60 <vtable for Derived>:  0x0000000000000000      0x0000000000400c90
0x400c70 <vtable for Derived+16>:       0x0000000000400b1e      0x0000000000000000
0x400c80 <vtable for Base+8>:   0x0000000000400cb8      0x0000000000400b1e
0x400c90 <typeinfo for Derived>:        0x0000000000601d98      0x0000000000400ca8
0x400ca0 <typeinfo for Derived+16>:     0x0000000000400cb8      0x6465766972654437
0x400cb0 <typeinfo name for Derived+8>: 0x0000000000000000      0x0000000000601d40
0x400cc0 <typeinfo for Base+8>: 0x0000000000400cc8      0x0000006573614234
0x400cd0:       0x0000006c3b031b01      0xfffffb200000000c
(gdb) info symbol 0x0000000000400b1e
Base::fool() in section .text of /home/lss/test/vtable_1d
(gdb)

foo()虚函数地址和基类一样:0x0000000000400b1e

图解如下,typeinfo忽略不展示:

由此可见,如果派生类没有重写基类的虚函数,派生类仍然有自己的虚表,但是里面的虚函数指向基类的虚函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2038473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

求解答matlab,具体问题如下:

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

数据埋点系列 6|数据驱动决策的实际应用:从理论到实践

在前面的系列文章中&#xff0c;我们深入探讨了数据驱动决策的各个方面。现在&#xff0c;是时候将这些知识付诸实践了。本文将通过一个虚构但贴近现实的案例&#xff0c;展示如何在一个组织中实施数据驱动决策&#xff0c;同时我们将讨论在这个过程中可能遇到的挑战和解决方案…

HAProxy 全解析:驾驭网络负载均衡与高可用的强大引擎

一、什么是HAproxy HAProxy是一个免费、开源的高性能TCP/HTTP负载均衡器和代理服务器软件&#xff0c;主要用于实现以下功能 一、负载均衡 多种负载均衡算法支持&#xff1a; 轮询&#xff08;Round Robin&#xff09;&#xff1a;它依次将请求均匀分配到后端的各个服务器。例…

Linux进程--进程的调度和切换

文章目录 一、进程优先级1.优先级的概念2.优先级的实现 二、进程的调度和切换1.Linux的进程调度概念2.进程的切换1.硬件上下文2.Linux调度实现 一、进程优先级 1.优先级的概念 进程要访问某种资源&#xff0c;要进行进程上的排队&#xff0c;来确认享受资源的前后顺序。 在x…

基于phpstudy对cmseasy5.5进行漏洞复现

目录&#xff1a; 漏洞复现的cmseasy5.5百度网盘链接 安装cmseasy&#xff1a; 1.在phpstudy上安装cmseasy 2.设置mysql密码为phpstudy内置mysql的密码并检查安装环境 3.安装后查看mysql内cmseasy是否有内容 获取用户名和密码过程&#xff1a; 1.查看源码发现有个remotelo…

字符串 - 反转字符串 II

541. 反转字符串 II 方法一&#xff1a;模拟&#xff08;reverse方法分开写&#xff09; /*** param {string} s* param {number} k* return {string}*/ var reverseStr function(s, k) {const n s.length;const arr Array.from(s);for (let i 0; i < n; i 2 * k) {re…

uniCloud服务空间选择支付宝云后,使用uni-file-picker扩展组件不回显的bug处理

问题概述 开发uniappunicloud项目&#xff0c;DCloud官方给出了很多好用的扩展组件&#xff0c;其中uni-file-picker图片上传组件非常好用&#xff0c;不用编写代码&#xff0c;即可将本地图片上传到云存储中。 原来云开发一直选择的是阿里云服务空间&#xff0c;官方一直推支…

汇编:基本指令及格式

汇编不区分大小写 寄存器只有名字&#xff0c;没有地址 Nop 栈先初始化 import引用外部函数 export外部可用声明 函数传参&#xff08;默认先使用寄存器R0~R3&#xff09;: 1.四个以内的参数使用 R0~R3 2.超过四个的参数使用栈传递 返回值: 返回值存放在R0中 立即数&…

还不会部署本地AI大模型?LangChat带你快速接入并部署本地大模型

LangChat是Java生态下企业级AIGC项目解决方案&#xff0c;在RBAC权限体系的基础上&#xff0c;集成AIGC大模型功能&#xff0c;帮助企业快速定制知识库、企业机器人。 产品官网&#xff1a;LangChat – LangChat LangChat源码&#xff1a;https://github.com/tycoding/langcha…

【机器学习】神经网络通过梯度下降学习的步骤以及前向传播的详细步骤

引言 神经网络中的梯度下降是一种优化算法&#xff0c;用于训练网络&#xff0c;即调整网络的权重和偏置&#xff0c;以最小化损失函数 文章目录 引言一、神经网络通过梯度下降学习的步骤1.1 初始化网络参数1.2 前向传播&#xff08;Forward Propagation&#xff09;1.3 计算损…

优化大量数据导出到Excel的内存消耗(二):如果数据超出Excel单表上限,则进行分表

优化前&#xff1a;优化大量数据导出到Excel的内存消耗_大文件异步导出 内存占用高-CSDN博客 写Excel文件报错&#xff1a;Invalid row number (1048576) outside allowable range (0..1048575) 写入Excel时遇到IllegalArgumentException&#xff0c;原因是超出允许的最大行数…

哪些岗位在拿着年薪百万?

​根据脉脉数据研究院 2020 年 8 月 2 日最新发布的《中国高端人才透视2020》(以下简称“报告”)&#xff0c;高端人才占比的最多的行业&#xff0c;其实不是互联网。 报告显示&#xff0c;以高薪金领人才相比于该行业的整体人才数量的占比来计算&#xff0c;排名前三的是金融、…

【分割大模型】SAM2(Segment Anything2)新的分割一切大模型(原理+安装+代码)

文章目录 前言1.特点2.结构3.应用场景 一、原理1.1 引言1.2 任务&#xff1a;可提示的视觉分割1.3 模型1.4 数据引擎 与 SA-V数据集 二、安装与使用 项目地址&#xff1a;https://github.com/facebookresearch/segment-anything-2 前言 Segment Anything Model 2&#xff08;S…

使用SSL认证访问操作手册

完整版 【金山文档 | WPS云文档】 使用SSL认证访问操作手册 https://kdocs.cn/l/cuxGfHD17eEw

vue2.0和vue3.0区别

vue2.0和vue3.0区别 双向数据绑定的原理改变&#xff1a;‌ Vue2使用Object.defineProperty对数据进行劫持&#xff0c;‌结合发布订阅模式实现双向数据绑定&#xff0c;‌而Vue3则采用了ES6的Proxy API对数据进行代理&#xff0c;‌提供了更多的拦截操作&#xff0c;‌能够监…

KBL406-ASEMI、AI智能专用整流桥KBL406

编辑&#xff1a;ll KBL406-ASEMI、AI智能专用整流桥KBL406 型号&#xff1a;KBL406 品牌&#xff1a;ASEMI 封装&#xff1a;KBL-4 批号&#xff1a;2024 现货&#xff1a;50000 正向电流&#xff08;Id&#xff09;&#xff1a;4A 反向耐压&#xff08;VRRM&#xff…

【智能流体力学】ANSYS Fluent流体仿真基础深度学习驱动的前期准备:CAX计算机辅助集成技术

目录 一、CAX计算机辅助集成技术二、计算机辅助工程(CAE)三、SCDM (Species Concentration Display Model) 显示和分析物质浓度分布的模型1. **SCDM概述**2. **主要功能**3. **功能特点**4. **使用步骤**5. **应用实例**6. **优点与限制**四、行业应用五、Fluent 软件功能1. …

net.sf.jsqlparser.statement.select.SelectItem

今天一启动项目&#xff0c;出现了这个错误&#xff0c;仔细想了想&#xff0c;应该是昨天合并代码&#xff0c;导致的mybatis-plus版本冲突&#xff0c;以及分页PageHelper版本不兼容 可以看见这个我是最下边的 Caused by 报错信息&#xff0c;这个地方提示我 net .s…

Java面试题--JVM大厂篇之实战解析:如何通过CMS GC优化大规模Java应用的响应时间

引言&#xff1a; 下午好&#xff0c;各位Java开发者&#xff01;在实际项目中&#xff0c;性能优化一直是我们关注的重点&#xff0c;特别是在面对大规模Java应用时&#xff0c;响应时间的优化更是至关重要。今天&#xff0c;我们将通过实战案例&#xff0c;深入解析如何利用C…

【JAVA多线程】JDK线程同步工具类:Semaphore、CountDownLatch、CyclicBarrier

目录 1.可能会遇到的线程协作场景 2.Semaphore 3.CountDownLatch 4.CyclicBarrier 1.可能会遇到的线程协作场景 在并发编程中&#xff0c;线程除了独自向前运行&#xff0c;还可能相互之间要进行协作&#xff0c;以保证完成最终总的目标。可能会遇到的几种任务之间的协作&…