解析c++空指针解引用奔溃

空指针解引用引起程序奔溃是c/c++中最常见的稳定性错误之一。
显然并非所有使用空指针的语句都会导致奔溃,那什么情况下使用空指针才会引起程序奔溃呢?有一个判断标准:判断空指针是否会导致访问非法内存的情况,如果会导致访问非法内存就会奔溃,否则不会奔溃

常见的空指针操作

考虑下面的代码,用到空指针test的6条语句(#1#6)中哪些会引起程序奔溃?

struct Test {
    void method_01() {  }
    virtual void method_02() {  };
    int value;

    static void StFunction() {  }
    static int stValue;
};
int Test::stValue = 1;

int main() {
    Test* test = nullptr;

    Test copy = *test;           // #1
    int value = test->value;     // #2    
    int stVAlue = test->stValue; // #3
    test->StFunction();          // #4
    test->method_01();           // #5
    test->method_02();           // #6
}

答案如下

序号代码含义是否会引起程序奔溃
#1对指针取值
#2通过指针访问成员变量
#3通过指针访问静态变量
#4通过指针调用静态函数
#5通过指针调用成员函数
#6通过指针调用虚函数

面对这个答案大家可能会有疑问:

  • 为什么空指针test访问成员变量会奔溃而访问静态变量不会奔溃?
  • 为什么空指针test调用静态函数和非虚成员函数不会奔溃而调用虚函数会奔溃?

原因隐藏在“对空指针解引用会引发程序奔溃”这句话的关键词解引用里。怎么理解引用呢?可以简单理解为访问与指针有关的内存地址。

从程序运行的角度来看,问题的本质是访问非法内存会引起程序奔溃。所以空指针是否会引起程序奔溃的一个判断标准总结为:判断空指针是否会导致访问非法内存的情况,如果会导致访问非法内存就会奔溃,否则不会奔溃

接下来我们逐个分析#1#6这些语句的内存访问情况,会涉及到一些c++底层知识,也是本文的主要内容。

深入理解

在详细分析之前先看看Test类的内存结构
在这里插入图片描述

注意:虚函数表和虚函数表指针不是必须的,只有定义或者继承了虚函数的类型才会分配这两块内存。

内存分为两部分(可以结合进程的内存结构和ELF文件结构来理解):
静态内存:编译阶段确定地址的内存,与实例无关且全局只存在一份。如静态变量、虚函数表、代码段。
动态内存:运行阶段才能确定地址的内存,与实例绑定。如成员变量、虚函数表指针(虚表指针实际上也是成员变量,特殊在它是由编译器添加的)。

所以用到空指针test的6条语句本身访问内存情况如下

编号操作访问符号符号类型符号地址备注
Test* test = nullptr;----
#1Test copy = *test;test指针类型局部变量--
#2int value = test->value;Test::value成员变量0x8value相对Test首地址的偏移量为8字节,因此地址为 0x0 + 8 = 0x8
#3int stVAlue = test->stValue;Test::stValue静态变量固定地址编译阶段分配好的地址,与指针test无关
#4test->StFunction();Test::StFunction静态函数固定地址编译阶段分配好的地址,与指针test无关
#5test->method_01();Test::method_01非虚成员函数固定地址编译阶段分配好的地址,与指针test无关
#6test->method_02();虚函数表指针指针类型成员变量0x0虚函数表指针 相对Test首地址的偏移量为0字节,因此地址为 0x0 + 0 = 0x0
Test::method_02虚函数固定地址编译阶段分配好的地址,与指针test无关

了解Test的内存结构之后,分析空指针test的6条语句是否会引起程序奔溃就变得清晰很多:

#1取值操作:Test copy = *test;

空指针test指向的地址是0x0,取值操作*test访问的是非法内存地址0x0,所以会引起程序奔溃。

#2访问成员变量:int value = test->value;

test->value是在访问非法地址0x8,所以会引起程序奔溃。

#3访问静态变量:int stVAlue = test->stValue;

访问静态变量和静态函数的方式有2种
通过实例访问:例如 int stVAlue = test->stValue;test->StFunction();
通过类名访问:例如 int stVAlue = Test::stValue;Test::StFunction();
两种访问方式的效果是一样的,实际上通过类名访问的方式更常见。本文使用通过实例访问的方式做示例是为了与其他操作做对比。

将示例代码访中问静态变量和静态函数的语句替换成通过类名访问的方式后,会发现访问静态变量和调用静态函数的语句与test指针本身没有半毛钱关系。

test->stValue等价于Test::stValue,这条语句访问的是stValue的地址而这个地址必然是有效的,与空指针test没有任何关系,所以不会引起程序奔溃。

#5调用非虚成员函数:test->method_01();

成员函数的本质
从内存结构上看成员函数和静态函数似乎没有区别,实际上他俩确实没有区别。可以这样理解:c++是比c语言多了很多特性的增强版,成员函数就是其中一个特性,这个特性类似于语法糖,目的是为了简化调用成员函数(一种特殊的函数)的语法。

成员函数特殊在第一个形参一定是this指针(隐式形参,不需要明确定义,编译器会在编译阶段补全),所以我们可以把成员函数退化成等价的c风格全局函数,例如
定义退化:成员函数void Test::method_01()可以退化成全局函数void method_01(Test* self)
调用退化:调用成员函数test->method()可以退化成调用全局函数method_01(test)

同样,test->method_01相当于method_01(test),是在访问method_01的地址而这个地址必然是有效的,虽然入参test是空指针,但调用函数这条语句本身不会访问这个空指针的内存,因此不会引起程序奔溃。

注意:调用非虚成员函数这条语句本身不会引起奔溃,但由于通常情况下成员函数的实现都会访问成员变量,所以程序可能会在成员函数内部因为解引用空指针this(也就是入参test)而奔溃。最常见具有迷惑性的奔溃现场比如
● 构造函数的内部空指针错误 —— 在访问成员变量或者虚函数的语句奔溃;
● 非虚析构函数内部空指针错误 —— 在访问成员变量或者虚函数的语句奔溃;
构造函数和非虚析构函数是特殊的非虚成员函数,在分析奔溃问题的时候可以把他们当作普通的非虚成员函数一样对待。

#6调用虚函数:test->method_02();

虚函数调用过程
虚函数是c++多态的核心技术(不知道多态是什么的同学出门右转找个角落自己学习一下),保证在继承结构中能正确调用子类的实现。虚函数表、虚函数表指针就是用来完成虚函数调用的,调用虚函数主要有下面几个步骤:
● 通过虚函数指针访问对应的虚函数表;例如Test的实例的虚函数指针指向Test的虚函数表;
● 在虚函数表中找到需要调用的函数;
● 调用这个函数;

调用虚函数的情况与调用非虚函数有所不同,test->method_02()不会直接访问函数method_02()的地址,而是首先通过虚函数表指针访问虚函数表,在通过空指针test访问虚函数指针时会访问非法地址0x0,因此会引起程序奔溃。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深度学习算法应用实战 | 利用 CLIP 模型进行“零样本图像分类”

文章目录 1. 零样本图像分类简介1.1 什么是零样本图像分类?1.2 通俗一点的解释 2. 模型原理图3. 环境配置4. 代码实战5. Gradio前端页面5.1 什么是 Gradio ? 6 进阶操作7. 总结 1. 零样本图像分类简介 1.1 什么是零样本图像分类? “零样本图像分类”(Zero-shot …

CentOS 6 制作openssl 1.1.1w rpm包 —— 筑梦之路

参考资料: CentOS 7 制作openssl 1.1.1w 版本rpm包 —— 筑梦之路_centos7 openssl 1.1.1 rpm包-CSDN博客 直接上spec文件如下: Name: openssl Version: 1.1.1w Release: 1%{?dist} Summary: Utilities from the general purpose cryptography li…

yolo 分割label格式标注信息图片显示可视化查看

参考: https://github.com/ultralytics/ultralytics/issues/3137 https://blog.csdn.net/weixin_42357472/article/details/135218349?spm=1001.2014.3001.5501 需要把坐标信息在图片上显示 代码 1)只画出了坐标边缘 import cv2 import numpy as np from random impor…

上海雏鸟科技无人机灯光秀跨年表演点亮三国五地夜空

2023年12月31日晚,五场别开生面的无人机灯光秀跨年表演在新加坡圣淘沙、印尼雅加达、中国江苏无锡、浙江衢州、陕西西安等五地同步举行。据悉,这5场表演背后均出自上海的一家无人机企业之手——上海雏鸟科技。 在新加坡圣淘沙西乐索海滩,500架…

【读书笔记】网空态势感知理论与模型(七)

通过网络级对象依赖关系揭示0Day攻击路径 1. 研究动机 0Day攻击是攻击者和防御者之间信息不对称的结果。赛门铁克研究人员认为,典型的0Day攻击平均隐藏312天才会被觉察。 受到SKRM模型启发,Patrol系统以全局视野来调查在某一个路径上的0Day攻击行为。从…

16_线程池

文章目录 完整的线程状态转换图理论层面代码层面 线程池3种线程池线程池的使用 多线程的实现方式三:实现Callable接口单例设计模式(线程安全) 完整的线程状态转换图 理论层面 代码层面 线程池 提高效率 3种线程池 Executors: 线程工具类,…

统一网关 Gateway【微服务】

文章目录 1. 前言2. 搭建网关服务3. 路由断言工厂4. 路由过滤器4.1 普通过滤器4.2 全局过滤器4.3 过滤器执行顺序 5. 跨域问题处理 1. 前言 通过前面的学习我们知道,通过 Feign 就可以向指定的微服务发起 http 请求,完成远程调用。但是这里有一个问题&am…

【JAVA】线程的run()和start()有什么区别?

🍎个人博客:个人主页 🏆个人专栏: JAVA ⛳️ 功不唐捐,玉汝于成 目录 前言 正文 run() 方法: start() 方法: 区别总结: 结语 我的其他博客 前言 多线程编程是Java中一个重要…

生成式 AI 如何重塑软件开发流程和开发工具?

生成式AI正在重塑开发流程和开发工具,通过自动化和优化软件开发过程,提高开发效率和质量。它可以帮助开发人员快速生成代码、测试和部署应用程序,同时减少错误和缺陷。此外,生成式AI还可以帮助开发人员快速理解和解决复杂的技术问…

Vulnhub靶机:Corrosion1

一、介绍 运行环境:Virtualbox 攻击机:kali(10.0.2.15) 靶机:corrosion:1(10.0.2.12) 目标:获取靶机root权限和flag 靶机下载地址:https://www.vulnhub.com/entry/c…

Vue3-46-Pinia-获取全局状态变量的方式

使用说明 在 Pinia 中,获取状态变量的方式非常的简单 : 就和使用对象一样。 使用思路 : 1、导入Store;2、声明Store对象;3、使用对象。 在逻辑代码中使用 但是 Option Store 和 Setup Store 两种方式定义的全局状态变量…

0109作业

1> 思维导图 2> 使用手动连接,将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中,在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中,在槽函数中判断ui界面上输入的账号是否为"admin&quo…

设计与实现基于Java+MySQL的模拟银行ATM操作系统

课题背景 随着现代经济的发展,电子支付和自动化银行服务已成为人们生活中不可或缺的一部分。自动取款机(ATM)作为一种常见的自助服务设备,使用户能够方便地进行资金的存取、查询余额、转账等操作,而无需到银行柜台。 …

浅淡A100-4090-性价比

大模型的训练用 4090 是不行的,但推理(inference/serving)用 4090 不仅可行,在性价比上还能比 H100 稍高。4090 如果极致优化,性价比甚至可以达到 H100 的 2 倍。 -------------------- FP64(双精度浮点&a…

pycharm社区版配置flask开发环境

新建配置文件,类型选择Shell Script 设置Execute中flask.exe的路径 设置options :--appflask_app.py run --port5000 --debug 设置working 路径 设置环境变量FLASK_APPflask_app.py;FLASK_ENVdevelopment 注意:FLASK_APPflask_app.py和上…

automa插件使用的一些经验

automa,我承认我写不出来这样的代码,早年的时候公司想过做一个爬虫的工具,那个时候RPA还没有火,虽然下载也没怎么火.RPA再牛,还是需要工程师,想一点经验都没有人来做,还是理解不了。能够简化数据…

Java接口的解析

在 Java 中,接口(Interface)是一种抽象类型,用于定义一组相关方法的契约。接口只包含方法的签名,而没有方法的实现。实现接口的类必须提供接口中定义的方法的具体实现。 以下是对 Java 接口的解析: 这只是…

【uniapp】遇到的一些问题

一、小程序中textarea ios样式不生效的方法 默认有内边距,加个disable-default-padding"true" 二、uni-data-picker循环使用,一个改了全局的值 换成了uni自带的picker,下面括号里必须有默认值,为空字符串的时候&…

国产AI工具钉钉AI助理:开启个性化助手服务的新篇章

钉钉AI助理是钉钉平台的一项功能,它可以根据用户的需求提供个性化的AI助手服务。用户可以在AI助理页面一键创建个性化的AI助理,如个人的工作AI助理、旅游AI助理、资讯AI助理等。企业也可以充分使用企业所沉淀的知识库和业务数据,在获得授权后…

Python实现PDF—>Excel的自动批量转换(附完整代码)

Python实现PDF—>Excel的自动批量转换(附完整代码) 话不多说,先看效果! 需要转换的PDF: 转换后的Excel: 01、底层原理 PDF 到 Excel 的转换涉及不同文件格式之间的数据提取和重构。底层原理可以简…