C++ 内存布局 - Part2: 从内存布局角度看类型转换 static_cast, dynamic_cast, reinterpret_cast

news2024/9/21 19:00:29

0. 总论

开门见山,先把结论写在这里:

1)static_cast 在做基类指针和派生类指针之间的转换时,会根据编译时的静态偏移操作指针,但是没有运行期的类型安全检查,程序员需要自己确保类型的正确性,比如派生类指针确实指向了派生类对象。

2)dynamic_cast 依赖于虚表做运行期类型检查,适用于有虚函数的类型转换。

3)reinterpret_cast是最不安全的类型转换,完全暴力强制转换。

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

1. 没有虚函数的单继承

1.1 static_cast

由于是单继承,派生类对象的内存起始部分就是基类信息,而static_cast也不做运行期的类型检查,因此派生类对象指针和基类指针可以自由转换。

转换前后指针都指向派生类对象起始地址。

1.2 dynamic_cast

dynamic_cast需要做运行时类型检查,向上转换(派生类到基类)没有问题,因为派生类的内存布局中起始部分就是基类信息,但是对于向下转换(基类到派生类)来说,在没有虚函数(虚表)的情况下,无法关联typeinfo信息,没法保证一个基类指针指向的是不是派生类。

1.3 reinterpret_cast

暴力转换,转换前后指针都指向派生类对象起始地址。

1.4 三种转换的示例代码:

#include <iostream>

class Base {
public:
    int baseValue;
    Base(int v) : baseValue(v) {}
    void fool() {
        std::cout << "Base class fool()" << std::endl;
    }
};

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

int main() {
    Derived* dPtr = new Derived(20,30); // 创建 Derived 对象
    std::cout << "sizeof(Base): " << sizeof(Base) << std::endl;
    std::cout << "sizeof(Derived): " << sizeof(Derived) << std::endl;

    std::cout << "Derived Ptr: " << dPtr << std::endl;

    // 1. 使用 static_cast
    std::cout << "### Using static_cast:" << std::endl;
    Base* basePtr1 = static_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "static_cast basePtr1: " << basePtr1 << std::endl;
    basePtr1->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr1->baseValue is " << basePtr1->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr1 = static_cast<Derived*>(basePtr1); // Base* 转换为 Derived*
    std::cout << "static_cast derivedPtr1: " << derivedPtr1 << std::endl;
    derivedPtr1->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr1->baseValue is " << derivedPtr1->baseValue << std::endl;
    std::cout << "derivedPtr1->derivedValue is " << derivedPtr1->derivedValue << std::endl;

    // 2. 使用 dynamic_cast
    std::cout << "\n### Using dynamic_cast:" << std::endl;
    Base* basePtr2 = dynamic_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "dynamic_cast basePtr2: " << basePtr2 << std::endl;
    basePtr2->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr2->baseValue is " << basePtr2->baseValue << std::endl;
    // 由于没有虚函数,dynamic_cast 无法正常使用,这行代码会导致编译错误
    //Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2);

    // 3. 使用 reinterpret_cast
    std::cout << "\n### Using reinterpret_cast:" << std::endl;
    Base* basePtr3 = reinterpret_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "reinterpret_cast basePtr3: " << basePtr3 << std::endl;
    basePtr3->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr3->baseValue is " << basePtr3->baseValue << std::endl;

    // 从 Base* 强制转换回 Derived*
    Derived* derivedPtr3 = reinterpret_cast<Derived*>(basePtr3); // 将 Base* 强制转换为 Derived*
    std::cout << "reinterpret_cast derivedPtr3: " << derivedPtr3 << std::endl;
    derivedPtr3->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr3->baseValue is " << derivedPtr3->baseValue << std::endl;
    std::cout << "derivedPtr3->derivedValue is " << derivedPtr3->derivedValue << std::endl;

    // 清理内存
    delete dPtr;

    return 0;
}

运行结果:

$ ./cast1
sizeof(Base): 4
sizeof(Derived): 8
Derived Ptr: 0x25a8eb0
### Using static_cast:
static_cast basePtr1: 0x25a8eb0
Base class fool()
basePtr1->baseValue is 20
static_cast derivedPtr1: 0x25a8eb0
Derived class fool()
derivedPtr1->baseValue is 20
derivedPtr1->derivedValue is 30

### Using dynamic_cast:
dynamic_cast basePtr2: 0x25a8eb0
Base class fool()
basePtr2->baseValue is 20

### Using reinterpret_cast:
reinterpret_cast basePtr3: 0x25a8eb0
Base class fool()
basePtr3->baseValue is 20
reinterpret_cast derivedPtr3: 0x25a8eb0
Derived class fool()
derivedPtr3->baseValue is 20
derivedPtr3->derivedValue is 30

1.5  内存布局图解

2. 有虚函数的单继承

2.1 示例代码

上面的代码稍作修改,将fool()变成虚函数,同时dynamic_cast向下转换可以生效:

#include <iostream>

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

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

int main() {
    Derived* dPtr = new Derived(20,30); // 创建 Derived 对象
    std::cout << "sizeof(Base): " << sizeof(Base) << std::endl;
    std::cout << "sizeof(Derived): " << sizeof(Derived) << std::endl;

    std::cout << "Derived Ptr: " << dPtr << std::endl;

    // 1. 使用 static_cast
    std::cout << "### Using static_cast:" << std::endl;
    Base* basePtr1 = static_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "static_cast basePtr1: " << basePtr1 << std::endl;
    basePtr1->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr1->baseValue is " << basePtr1->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr1 = static_cast<Derived*>(basePtr1); // Base* 转换为 Derived*
    std::cout << "static_cast derivedPtr1: " << derivedPtr1 << std::endl;
    derivedPtr1->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr1->baseValue is " << derivedPtr1->baseValue << std::endl;
    std::cout << "derivedPtr1->derivedValue is " << derivedPtr1->derivedValue << std::endl;

    // 2. 使用 dynamic_cast
    std::cout << "\n### Using dynamic_cast:" << std::endl;
    Base* basePtr2 = dynamic_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "dynamic_cast basePtr2: " << basePtr2 << std::endl;
    basePtr2->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr2->baseValue is " << basePtr2->baseValue << std::endl;

    // 从 Base* 转回 Derived*
    Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2);
    std::cout << "dynamic_cast derivedPtr2: " << derivedPtr2 << std::endl;
    derivedPtr2->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr2->baseValue is " << derivedPtr2->baseValue << std::endl;
    std::cout << "derivedPtr2->derivedValue is " << derivedPtr2->derivedValue << std::endl;

    // 3. 使用 reinterpret_cast
    std::cout << "\n### Using reinterpret_cast:" << std::endl;
    Base* basePtr3 = reinterpret_cast<Base*>(dPtr); // Derived* 转换为 Base*
    std::cout << "reinterpret_cast basePtr3: " << basePtr3 << std::endl;
    basePtr3->fool(); // 调用 Base 的 fool()
    std::cout << "basePtr3->baseValue is " << basePtr3->baseValue << std::endl;

    // 从 Base* 强制转换回 Derived*
    Derived* derivedPtr3 = reinterpret_cast<Derived*>(basePtr3); // 将 Base* 强制转换为 Derived*
    std::cout << "reinterpret_cast derivedPtr3: " << derivedPtr3 << std::endl;
    derivedPtr3->fool(); // 调用 Derived 的 fool()
    std::cout << "derivedPtr3->baseValue is " << derivedPtr3->baseValue << std::endl;
    std::cout << "derivedPtr3->derivedValue is " << derivedPtr3->derivedValue << std::endl;

    // 清理内存
    delete dPtr;

    return 0;
}

运行结果;

$ ./cast2
sizeof(Base): 16
sizeof(Derived): 16
Derived Ptr: 0x222ceb0
### Using static_cast:
static_cast basePtr1: 0x222ceb0
Derived class fool()
basePtr1->baseValue is 20
static_cast derivedPtr1: 0x222ceb0
Derived class fool()
derivedPtr1->baseValue is 20
derivedPtr1->derivedValue is 30

### Using dynamic_cast:
dynamic_cast basePtr2: 0x222ceb0
Derived class fool()
basePtr2->baseValue is 20
dynamic_cast derivedPtr2: 0x222ceb0
Derived class fool()
derivedPtr2->baseValue is 20
derivedPtr2->derivedValue is 30

### Using reinterpret_cast:
reinterpret_cast basePtr3: 0x222ceb0
Derived class fool()
basePtr3->baseValue is 20
reinterpret_cast derivedPtr3: 0x222ceb0
Derived class fool()
derivedPtr3->baseValue is 20
derivedPtr3->derivedValue is 30

2.2 内存布局图解

3. 没有虚函数的多继承

类图关系如下:

3.1 static_cast

可见,将Derived Ptr static_cast到Base2 Ptr以后,这个指针指向Derived instance内存布局当中Base2的部分起始地址。如果继续再做反方向的static_cast, 从 Base2 Ptr还可以回到Derived Ptr

3.2 dynamic_cast

没有虚函数的情况下,只允许派生类指针到基类指针的转换,基类指针不允许转为派生类指针。

3.3  reinterpret_cast

暴力转换,强制类型转换

此时,如果通过Base2 *b2ptr去访问Base2部分的内存内容会出问题,它并不像static_cast或者dynamic_cast那样做了指针偏移。

4. 有虚函数的多继承

Derived实例化以后的内存布局示意图:

4.1  static_cast

4.2 dynamic_cast 

4.3  reinterpret_cast

暴力转换,强制转换

此时,如果通过Base2 *b2ptr去访问Base2部分的内存内容会出问题,比如:

操作 b2ptr->baseValue2其实操作的是baseValue1

操作 b2ptr->foo2() 实际操作的是foo1()

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

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

相关文章

ollama轻松部署本地GraphRAG(避雷篇)

本篇文章主要介绍如何使用ollama本地部署微软的Graph RAG&#xff0c;&#xff0c;Graph RAG成为RAG一种新范式&#xff0c;对于全局总结性问题表现突出&#xff0c;当开始的技术路线是Qwen2vllmfastchat本地部署Grapg RAG&#xff0c;但是embedding 的openai服务怎么都跑不起来…

完整搭建windows下mysql8.0源码编译调试环境!

背景&#xff1a; 前段时间一直在看mysql相关的博客&#xff0c;所以对源码起了浓厚的兴趣&#xff0c;所以尝试通过vmware和vscode在windosw环境中搭建一套编译调试的环境~ 看了一下网上的搭建教程基本杂乱无章&#xff0c;想要从零跟着搭建出一个完善的调试环境也不是易事&…

redis I/O复用机制

I/O复用模型 传统阻塞I/O模型 串行化处理&#xff0c;就是要等&#xff0c;假如进行到accept操作&#xff0c;cpu需要等待客户端发送的数据到tcp接收缓冲区才能进行read操作&#xff0c;而在此期间cpu不能执行任何操作。 I/O复用 用一个进程监听大量连接&#xff0c;当某个连…

国产大模型领域跳槽:收入潜力解析

夏尽秋来&#xff0c;2024年国产大模型看似喧闹已止&#xff0c;进入稳定竞争期。 作为一种新的IT解决方案&#xff0c;国产大模型一出生便伴随着激烈竞争。 外有GPT4&#xff0c;内有多家公司角逐“中国版ChatGPT”。 据我所知&#xff0c;就国内某家头部大模型创业公司的收…

0817(持久层框架:JDBC,MyBatis)

三层架构&#xff08;表现层&#xff0c;业务层&#xff0c;持久层&#xff09; java中框架的概述&#xff08;表现层、业务层、持久层的关系&#xff09;_控制层业务层持久层的关系-CSDN博客 框架&#xff1a;框架一般处在低层应用平台&#xff08;如J2EE&#xff09;和高层…

利用keepalived达成服务高可用

官方网站Keepalived for Linux 1.keepalived简介 vrrp 协议的软件实现&#xff0c;原生设计目的为了 高可用 ipvs 服务 功能&#xff1a; 基于 vrrp 协议完成地址流动 为 vip 地址所在的节点生成 ipvs 规则 ( 在配置文件中预先定义 ) 为 ipvs 集群的各 RS 做健康状态检测 …

【Linux网络】NAT技术

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 引言 随着互联网的飞速发展&#xff0c;IP地址资源日益紧张&#xff0c;这促使了NAT&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;技术的诞生与发展。NAT技术不仅解决了IPv4…

webshell免杀--免杀入门

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要整理webshell免杀的一些基础思路 入门级&#xff0c;不是很深入&#xff0c;主要是整理相关概念 免杀对象 1.各类杀毒软件 类似360&#xff0c;火绒等&#xff0c;查杀己方webshell的软件。 2.各类流量…

计算机网络系统速成

Http与Https Http与Https是两种重要的网络通信协议&#xff0c;它们在Web通信中扮演着关键角色。以下是对Http与Https的详细解析&#xff1a; 一、Http&#xff08;超文本传输协议&#xff09; 1. 定义与功能 定义&#xff1a;Http&#xff08;HyperText Transfer Protocol…

打卡学习Python爬虫第三天|电影天堂案例

一、明确需求 目标&#xff1a;爬取最新更新的电影的豆瓣链接 观察网页和页面源代码&#xff0c;每部电影都有一个超链接去到子页面&#xff0c;我们需要的内容在子页面&#xff0c;如果我们一个一个子页面的去爬取会比较麻烦&#xff0c;可以尝试先通过首页爬取子页面的超链…

指针初阶(指针类型转换的使用、指针数组)

一.指针基础 0.指针的大小 指针指向的是一块地址&#xff0c;所以指针存储的是地址&#xff0c;例如在32位系统中。定义了一个int32_t类型的变量。使用int32_t *定义一个指针&#xff0c;使其指向该变量。 设该变量存储在地址为00000000000000000000000000000001&#xff08;3…

远程调用-OpenFeign(一)

目录 1.RestTemplate存在问题 2.OpenFeign介绍 一、主要特点 二、应用场景 3.OpenFeign快速上手 3.1引入依赖 3.2添加注解 3.3编写OpenFeign的客户端 3.4远程调用 ​编辑3.5测试 4.OpenFeign参数传递 4.1传递单个参数 4.2传递多个参数 4.3传递对象 4.4传递JSO…

编程修炼之Hibernate--- springboot启动初始化ddl过程

文章目录 跟踪Springboot整合hibernate的启动代码&#xff1a; 开始初始化 entityManagerFactory 创建方言 dialect 继续排查

Koa商城项目-轮播图模块(后端)

前言 通过这次独自做前后端发现有很多需要提升的地方&#xff0c;很多细节处理不到位。下面简单看一下本人自己做的效果吧~~ Git地址 https://gitee.com/ah-ah-bao/koa_system 效果图 后端逻辑分析 首先编写route->banner.router.js /*** author: zxb* date: 2024-08-06…

Socket编程TCP 基础

一.什么是Socket(套接字&#xff09; 定义&#xff1a;就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端&#xff0c;提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲&#xff0c;套接字上联应用进程&#x…

【文献阅读】A Comprehensive Review of Multimodal Large Language Models

一、回顾 MLLMs 在语言、图像、视频和音频处理等多模态任务中表现出色。这些模型通过整合多模态信息来增强多模态任务的有效性。 在自然语言处理&#xff08;NLP&#xff09;任务中&#xff0c;如文本生成和机器翻译&#xff0c;MLLMs 利用图像、视频和音频提供上下文支持&am…

C#开发实验--卫星星历的计算、空间直角坐标和大地坐标的转换、ArcGIS Engine开发的简单实例

前不久在公众号GIS研发看到了暑假公益C#开发教程&#xff0c;教大家ArcGIS Engine开发。 想到了自己本科阶段也学习了C#开发和AE开发&#xff0c;学习了使用C#添加空间等&#xff0c;进行卫星星历的计算、空间直角坐标和大地坐标的转换、ArcGIS Engine开发的简单实例。 下面的…

数据结构与算法--插入排序与选择排序

文章目录 回顾提要排序基本概念排序的分类排序算法的稳定性排序算法的性能指标内排序 排序方法直接插入排序直接插入排序的要点直接插入排序的实现直接插入排序性能分析直接插入排序的适用情景 简单选择排序简单选择排序的要点简单选择排序的执行过程简单选择排序的实现简单选择…

虚幻5|布料模拟

打开骨骼网格体 1.Mass Prooerties 如果给角色施加风力&#xff0c;密度越大越难飘&#xff0c;相反密度越小飘动浮度也小 2.Material Proerties Edge Stiffness,对衣服的折痕处的调整&#xff0c;其值越大就越能维持原本的折痕&#xff0c;相反折痕就会变小&#xff0c;但…

【Mudo】实战项目之应用层模块

文章目录 前言正文1. Util1.1 File1.2 Url1.3 Str1.4 Infor 2. Http2.1 Request2.2 Response2.3 Context2.4 Server 尾序 前言 在上一篇文章当中&#xff0c;博主从代码的层面介绍了服务器模块的实现&#xff0c;最终封装出了一个传输层的TcpServer模块&#xff0c;那么在本篇…