当前位置: 首页 > news >正文

解析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;           // #1int value = test->value;     // #2    int stVAlue = test->stValue; // #3test->StFunction();          // #4test->method_01();           // #5test->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,因此会引起程序奔溃。

相关文章:

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

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

Oracle START WITH 递归语句的使用方法及示例

Oracle数据库中的START WITH语句经常与CONNECT BY子句一起使用,以实现对层次型数据的查询。这种查询模式非常适用于处理具有父子关系的数据,如组织结构、分类信息等。 理解START WITH和CONNECT BY 在层次型查询中,START WITH定义了层次结构…...

使用Windbg动态调试目标进程的一般步骤详解

目录 1、概述 2、将Windbg附加到已经启动起来的目标进程上,或者用Windbg启动目标程序 2.1、将Windbg附加到已经启动起来的目标进程上 2.2、用Windbg启动目标程序 2.3、Windbg关联到目标进程上会中断下来,输入g命令将该中断跳过去 3、分析实例说明 …...

Linux驱动学习—输入子系统

1、什么是输入子系统? 输入子系统是Linux专门做的一套框架来处理输入事件的,像鼠标,键盘,触摸屏这些都是输入设备,但是这邪恶输入设备的类型又都不是一样的,所以为了统一这些输入设备驱动标准应运而生的。…...

计算机网络(2)

计算机网络(2) 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU) 计算机网络和因特网(2)分组交换网中的时延、丢包和吞吐量时延丢包吞吐量总结 协议层次及其服务模型模型类型OSI模型分析TCP/IP模型分析 追溯历史 小程一言 我…...

什么是预训练Pre-training—— AIGC必备知识点,您get了吗?

Look!👀我们的大模型商业化落地产品📖更多AI资讯请👉🏾关注Free三天集训营助教在线为您火热答疑👩🏼‍🏫 随着人工智能(AI)不断重塑我们的世界,其发展的一个关键方面已经…...

bat脚本sqlserver 不同数据库同步

如果你想使用批处理脚本(.bat)在 SQL Server 中同步不同数据库的数据,你可以考虑以下步骤: 设置环境变量: 确保你的系统环境变量中已经设置了 SQLCMD 和 BCP 的路径。 编写批处理脚本: 使用 sqlcmd 来执行…...

阶段十-分布式-Redis02

第一章 Redis 事务 1.1 节 数据库事务复习 数据库事务的四大特性 A:Atomic ,原子性,将所以SQL作为原子工作单元执行,要么全部执行,要么全部不执行;C:Consistent,一致性&#xff0…...

微信小程序实战-02翻页时钟-2

微信小程序实战系列 《微信小程序实战-01翻页时钟-1》 文章目录 微信小程序实战系列前言计时功能实现clock.wxmlclock.wxssclock.js 运行效果总结 前言 接着《微信小程序实战-01翻页时钟-1》,继续完成“6个页面的静态渲染和计时”功能。 计时功能实现 clock.wxm…...

每天刷两道题——第十一天

1.1滑动窗口最大值 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输出&…...

Git提交规范

一. 修改类型 每个类型值都表示了不同的含义,类型值必须是以下的其中一个: feat:提交新功能fix:修复了bugdocs:只修改了文档style:调整代码格式,未修改代码逻辑(比如修改空格、格式…...

apache2的虚拟主机的配置

APACHE2的虚拟主机配置 本章中心概括: 虚拟web主机的初步认识,在redhat系列系统中如何配置,在Debian系列系统中如何配置。 什么是apache2虚拟主机: 简单点讲,就是在同一个物理机中配置多个虚拟主机,从而达…...

Provide/Inject 依赖注入(未完待续)

父组件传递给子组件数据,通过props,但是需要逐层传递 provide/Inject 的推出就是为了解决这个问题,它提供了一种组件之间共享此类值的方式,不必通过组件树每层级显示地传递props 目的是为了共享那些被 认为对于一个组件树而言是全局的数据 p…...

力扣173. 二叉搜索树迭代器

深度优先搜索 思路: 遍历二叉搜索树,左子树总比根节点小,右子树总比根节点大;先深度遍历左子树,然后返回其父节点,然后遍历其右子树节点;使用栈数据结构存储节点数据,借用其“后进先…...

电脑找不到d3dcompiler43.dll怎么修复,教你5个可靠的方法

d3dcompiler43.dll是Windows操作系统中的一个重要动态链接库文件,主要负责Direct3D编译器的相关功能。如果“d3dcompiler43.dll丢失”通常会导致游戏无法正常运行或者程序崩溃。为了解决这个问题,我整理了以下五个解决方法,希望能帮助到遇到相…...

5.3 Android BCC环境搭建(eadb版 上)

写在前面 eadb即eBPF Android Debug Bridge,它是基于adeb的重构。后者曾随aosp 10发布在platform/external目录下。 一,root权限 这里再HighLight下,当前整个专栏都是基于开发环境来展开的,也就是Android设备需要具有root权限。因此该专栏下每一篇博客都是默认了当前开发…...

【算法题】44. 通配符匹配

题目 给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 ? 和 * 匹配规则的通配符匹配: ? 可以匹配任何单个字符。 * 可以匹配任意字符序列(包括空字符序列)。 判定匹配成功的充要条件是:字符模式必须能…...

vscode配置与注意事项

中文设置 https://zhuanlan.zhihu.com/p/263036716 应用搜索输入“Chinese (Simplified) Language Pack for Visual Studio Code”并敲回车键 底部信息窗没有的话 首先使用快捷键ctrlshiftp,Mac用户使shiftcommandp,然后输入settings.json 将下面的选…...

设计模式篇章(3)——七种结构型模式

结构型设计模式主要思考的是如何将对象进行合理的布局来组成一个更大的功能体或者结构体,这个现在讲有点抽象,用大白话讲就是利用现有的对象进行组合或者配合,使得组合后的这个系统更加好。好是相对于不使用设计模式,按照自己的堆…...

Window端口占用处理

您好,我是码农飞哥(wei158556),感谢您阅读本文,欢迎一键三连哦。 💪🏻 1. Python基础专栏,基础知识一网打尽,9.9元买不了吃亏,买不了上当。 Python从入门到精…...

React Native 导航系统实战(React Navigation)

导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

【位运算】消失的两个数字(hard)

消失的两个数字(hard) 题⽬描述:解法(位运算):Java 算法代码:更简便代码 题⽬链接:⾯试题 17.19. 消失的两个数字 题⽬描述: 给定⼀个数组,包含从 1 到 N 所有…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...

【AI学习】三、AI算法中的向量

在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息&#xff0…...

Go语言多线程问题

打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

【LeetCode】算法详解#6 ---除自身以外数组的乘积

1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...

【UE5 C++】通过文件对话框获取选择文件的路径

目录 效果 步骤 源码 效果 步骤 1. 在“xxx.Build.cs”中添加需要使用的模块 ,这里主要使用“DesktopPlatform”模块 2. 添加后闭UE编辑器,右键点击 .uproject 文件,选择 "Generate Visual Studio project files",重…...