【异常案例分析】使用空指针调用函数(非虚函数)时,没有崩溃在函数调用处,而是崩在被调用函数内部

目录

1、问题说明

2、代码段地址与数据段地址

3、使用空指针调用BindWindow函数(非虚函数),没有崩在BindWindow函数的调用处,而是崩在函数内部

3.1、虚函数调用的二次寻址

3.2、崩溃在被调用函数内部

4、总结


C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html       同事那边最近遇到了一个空指针引发的崩溃,使用空指针调用一个成员函数,但并没有崩溃在函数调用处,而是崩溃在被调用函数内部。同事搞不懂这个细节,找到我,让我帮忙看看,给详细讲解一下具体的原因。今天我就把相关的细节详细说明一下,以供大家参考。

1、问题说明

       最近同事遇到一个关于程序崩溃点的问题,找到我,想问一下是原因。发生崩溃的代码上下文如下所示:

代码中使用iter.second.pVideDec指针调用了BindWindow函数。同事可以确定的是,iter.second.pVideDec指针的值为NULL导致的崩溃,但令同事感到疑惑的是,为什么没有崩溃在使用空指针调用BindWindow函数的函数调用处,而是崩溃在BindWindow函数内部(代码运行到BindWindow函数内部才触发了崩溃)?更进一步,进入BindWindow函数内部后,没有去call函数CallMedia就发生了崩溃,这又是什么原因呢?

       这两个问题其实很简单,但实际上大部分C++开发人员并不懂,也讲不清楚!

2、代码段地址与数据段地址

       我们在看问题之前,我们需要弄清楚代码段地址与数据段地址的区别。C++程序在运行时所占用的内存区域,一般可分为栈内存区、堆内存区、全局/静态内存区、文字常量内存区及程序代码区5大分区,如下所示:

程序中变量占用的内存属于数据段内存区,包含栈内存区、堆内存区、全局/静态内存区、文字常量内存区。

       程序启动时会将依赖的各个二进制模块以及程序主模块加载到进程空间中,这些二进制模块要占用内存空间,这些内存空间是从进程的内存空间划拨的。具体的是,二进制模块中的二进制代码占用内存空间,这些内存空间中存放的都是二进制代码,所以叫代码段内存区。C++代码中调用函数,从汇编代码的角度去看,就是去call函数的地址(代码段地址)。程序一旦启动起来,加载起来的所有模块中的函数地址就确定下来了。

       关于C++程序的五大内存分区的详细说明,可以查看我的文章:

实例详解C++程序的五大内存分区https://blog.csdn.net/chenlycly/article/details/120958761


       在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)

专栏1:该精品技术专栏的订阅量已达到10000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到210篇以上!欢迎订阅!)

C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931

本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法详细讲述了C++软件的调试方法与手段详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!

考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!

专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到300篇以上!

专栏2:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达8000多个,专栏文章已经更新到500多篇,持续更新中...)

C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html

以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!

专栏3:  

C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795

常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!

专栏4:   

VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585

将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。

专栏5: 

C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html

根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。


3、使用空指针调用BindWindow函数(非虚函数),没有崩在BindWindow函数的调用处,而是崩在函数内部

       使用空指针去调用BindWindow函数,是去call函数代码段地址,这个操作不会导致崩溃,因为这个过程中没访问不该访问的内存。BindWindow函数在程序启动起来后就确定了,去call这个函数地址没有问题,不会发生异常。但如果这个BindWindow是虚函数,则会崩溃在函数调用处。

3.1、虚函数调用的二次寻址

       如果这个BindWindow是虚函数,调用虚函数时要找到该虚函数的地址然后去call这个地址完成虚函数调用,在获取虚函数地址的过程中会涉及到二次寻址:(此处讲的寻址,是从汇编的角度去看的,从汇编代码的角度可以清晰地看到二次寻址的完整过程

1)读出类对象中虚函数表指针中的值,就是虚函数表的首地址,这是第一次寻址。读取虚函数表指针变量的值,就是读取虚函数表指针变量所在内存中的内容,首先要拿到虚函数表指针变量的内存地址,有了地址,才能读取内存中的内容。对于C++类(对象),从类中成员变量的内存排布上看,作为成员变量的虚函数表指针变量是排在首位的,所以虚函数表指针变量的首地址,就是当前C++类对象的首地址。如果当前C++对象地址为NULL,就会把NULL作为虚函数表指针的首地址去读取虚函数表指针的值,就会访问地址为0的内存,这个小地址是禁止访问的,所以会触发内存访问违例引发崩溃。
2)第一步中拿到虚函数表的首地址,找到虚函数表的位置,然后根据目标虚函数在当前类对象中的排位,得到地址偏移,然后到虚函数表中找到目标虚函数的地址,然后去call这个地址就完成虚函数调用了。这是第二次寻址。

       关于虚函数调用的二次寻址的详细说明,可以查看我的文章:(从汇编的角度去看)
几秒读懂C++虚函数调用的汇编代码实现https://blog.csdn.net/chenlycly/article/details/121046234

3.2、崩溃在被调用函数内部

       上面讲了,程序没有崩溃在BindWindow函数的调用处,这样代码就运行到这个BindWindow函数内部了。

BindWindow函数内部并没有做什么复杂的事情,就是去调用另一个函数CallMedia。程序的崩溃发生在调用CallMedia的这行代码上,为什么呢?因为在调用CallMedia函数时访问当前类的成员变量m_dwChanId,把该成员变量作为参数传递给CallMedia,这就意味着要访问到成员变量m_dwChanId,那具体是怎么访问到m_dwChanId成员变量的呢?还是要从汇编代码的角度去看,因为成员变量m_dwChanId是调用CallMedia的一个参数,所以在call这个CallMedia函数之前需要将成员变量m_dwChanId的值从内存中读出来,然后将该值push压到栈上,通过栈传递给被调用函数CallMedia。而当前传入的C++类对象的地址为NULL,而做成类成员变量的m_dwChanId的地址是相对所在类对象地址的偏移,所以此时m_dwChanId变量的首地址是个很小的地址,而很小的地址是禁止访问的,从而引发内存访问违例,导致程序崩溃。

       上面我们多次讲到小地址内存区,关于小地址内存区的详细说明,可以查看我的文章:在文章的第8节有详细的说明

引发C++程序内存错误的常见原因分析与总结https://blog.csdn.net/chenlycly/article/details/139596707如果BindWindow函数中没有访问所在类的成员变量的值,只是访问函数中的局部变量,则不会导致内存访问违例,不会引发程序崩溃。

       上面我们多次讲到了从汇编代码的角度去看问题分析问题,对于C++程序员,是很有必要了解并学习汇编。汇编代码不仅能帮我们理解很多高级语言不好理解的代码执行细节,而且可以辅助分析排查C++软件异常问题。关于为什么要学习汇编代码以及如何学习汇编,可以查看我的文章:

C/C++程序员为什么要了解汇编?了解汇编有哪些好处?如何学习汇编?https://blog.csdn.net/chenlycly/article/details/142795872

4、总结

       本例中遇到的问题场景,大部分C++开发人员搞不清楚引发崩溃的根本原因,只是知道空指针或者野指针可能会引发程序崩溃(访问空指针或野指针可不是一定会引发程序崩溃的,要从内存的角度的分析)。但作为有经验的开发人员,是需要搞清楚引发崩溃的根本原因的,必要时要结合汇编代码去理解去分析。

有些问题看上去似乎很简单,甚至理所当然,但是进一步深究的话,可能可以挖掘出一些细节的!遇到问题要多思考!

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

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

相关文章

锁定中科院1区TOP!融合LSTM与Attention做时间序列预测 !

Transformer虽火,但在数据少、要求稳的时序预测场景中,LSTM仍是首选。尤其加上注意力机制后,更是弥补了LSTM的短板,增强了性能,实现了更精确的预测。这种组合不仅应用场景广泛,工业界爱,学术界也…

在不可更改系统上构建数据响应机制的可选策略

在现代企业信息系统架构中,我们常常面临如下挑战:某个业务系统属于“不可变更系统”,我们既不能修改其业务逻辑,也不能对其核心代码做任何侵入式改动。但与此同时,我们又需要对该系统中的某些关键业务数据变更做出响应…

Docker 实战 -- cloudbeaver

文章目录前言文件目录docker-compose.yml网络连接前言 当你迷茫的时候,请点击 Docker 实战目录 快速查看前面的技术文章,相信你总能找到前行的方向 上一篇文章 Docker 实战 – Mysql 讲述了用 docker 搭建 mysql 数据库的过程, 连接数据库的工具很多, …

Rust × WebAssembly 项目脚手架详解

一、模板概览 模板生成方式核心用途典型角色wasm-pack-templatecargo generate …把 Rust 代码 打包成 npm 库「底层算法/组件」作者create-wasm-appnpm init wasm-app构建纯 JS/TS 项目,消费上面生成的 npm 包Web 前端/Node 服务rust-webpack-templatenpm init ru…

RSA 解密逻辑

以下是使用类的方式封装 RSA 解密逻辑,使其更易于调用和管理: from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_v1_5 import base64 class RSADecryptor:"""RSA 解密工具类,封装解密逻辑,方便…

Oracle 19C 在centos中安装操作步骤和说明

1、oracle 一到五,是在centos下,搭建数据的每个具体详细步骤。 oracle:一、环境准备-CSDN博客 oracle:二、centos下安装oracle-CSDN博客 oracle :三、配置LISTEN-CSDN博客 oracle:四、创建数据库-CSDN博客 oracle:五、配置修改-CSDN博客…

《前端无障碍设计的深层逻辑与实践路径》

一个Web应用的价值不仅在于其功能的丰富性,更在于它能否向所有用户敞开大门。那些被忽略的交互细节—一段没有替代文本的图片、一个无法通过键盘触发的按钮、一组对比度不足的文字——正在悄然构建起一道无形的壁垒,将部分用户隔绝在数字世界之外。前端无障碍设计(A11y)的本…

ctfshow-web入门-254-266-反序列化

web254 代码审计&#xff0c;输入给的username和password ?usernamexxxxxx&passwordxxxxxx web255 这题要从cookie中获取值并且需要把isVip设为true&#xff0c;并且将序列化之后的结果进行url编码 <?php class ctfShowUser{public $usernamexxxxxx;public $passw…

ssh服务器端口和本地端口映射

由于服务器防火墙设置&#xff0c;本地能ssh登录远程服务器&#xff0c;但本地不能通过http的方式访问服务&#xff0c;如tensorborad、gradle或其他服务。在不需要修改防火墙安全设置的情况下&#xff0c;这里我们临时通过ssh端口映射的方式&#xff0c;在本地浏览器访问这些服…

计算机网络——UDP

1. UDP的背景 1&#xff09;先有TCP&#xff0c;后觉笨重 在TCP被首次提出后&#xff0c;将“可靠传输&#xff0c;流量控制&#xff0c;拥塞控制”全做在一个协议里随着应用增多 ——> 很多场景&#xff08;语音&#xff0c;视频&#xff09;并不需要万无一失 ——> 更…

常见的深度学习模块/操作中的维度约定(系统性总结)

&#x1f7e9; 1. 数据张量&#xff08;特征图&#xff09;维度这是我们喂进网络或从网络中出来的“实际数据”。类型维度格式举例说明图像/特征图(B, C, H, W)(4, 3, 32, 32)PyTorch中最常用的数据布局&#xff08;NCHW&#xff09;图像/特征图&#xff08;TensorFlow风格&…

【笔记】重学单片机(51)(上)

为学习嵌入式做准备&#xff0c;重新拿起51单片机学习。此贴为学习笔记&#xff0c;仅记录易忘点&#xff0c;实用理论基础&#xff0c;并不是0基础。 资料参考&#xff1a;清翔零基础教你学51单片机 51单片机学习笔记1. C语言中的易忘点1.1 数据类型1.2 位运算符1.3 常用控制语…

Arrays.asList() add方法报错java.lang.UnsupportedOperationException

1. 问题说明 记录一下遇到的这个bug&#xff0c;下面是段个简化后的问题重现代码。 public class Test {public static void main(String[] args) {List<Integer> list Arrays.asList(1, 2, 3);list.add(4);} }2. 原因分析 我们看一下Arrays.asList(…) 的源码&#xff…

克罗均线策略思路

一个基于移动平均线的交易策略&#xff0c;主要通过比较不同周期的移动平均线来生成买卖信号。该策略交易逻辑思路和特点&#xff1a;交易逻辑思路1. 多头交易逻辑&#xff1a;- 当当前周期的收盘价高于其4周期移动平均线&#xff0c;并且4周期移动平均线高于9周期移动平均线&a…

Go语言--语法基础7--函数定义与调用--自定义函数

函数是基本的代码块&#xff0c;用于执行一个任务。Go 语言最少有 1 个 main() 函数。你可以通过函数来划分不同功能&#xff0c;逻辑上每个函数执行的是指定的任务。函数声明告诉了编译器函数的名称、返回类型和参数。函数三要素名称 》功能参数 》接口返回值 》结果函数分类内…

Ollama模型库模型下载慢完美解决(全平台)

前言在我们从ollama下载模型时,会发现ollama最开始下载速度很快,能达到10-20MB/s但到了后期,速度就会越来越慢,最终降低到10-20kb/s下载一个模型大多需要1到1.5小时这是因为ollama服务器负荷过大的问题思路如果在下载中终断下载,在用ollama run恢复下载,速度就会又提上去,但3-4…

web:js的模块导出/导入

一般web页面中&#xff0c;html文件通过标签script引用js文件。但是js文件之间的引用要通过import/exprot进行导入/导出&#xff0c;同时还要在html文件中对js文件的引用使用type属性标注。在下面的例子中&#xff0c;html页面<!DOCTYPE html> <html lang"en&quo…

关于Web前端安全防御之安全头配置

一、核心安全头的作用 1. X-Content-Type-Options: nosniff 该响应头用于阻止浏览器对资源的 MIME 类型进行 “嗅探”&#xff08;猜测&#xff09;&#xff0c;强制浏览器严格遵守服务器返回的 Content-Type 头部声明。 风险背景&#xff1a; 浏览器默认会对未明确声明类型…

C++ : 反向迭代器的模拟实现

一、reverse_iterator.h#pragma once namespace txf { //外界传什么类型的iteator&#xff0c;它就用什么iterator 初始化 , list用_list_iterator<T,T&,,T*> ,vector<T> 用T*template<class Iterator,class Ref,class Ptr>//在这个反向迭代器中涉及到…

自动化与配置管理工具 ——SaltStack

一、SaltStack 概述1.1 核心特性SaltStack 是一款开源的自动化运维工具&#xff0c;采用客户端 - 服务器&#xff08;C/S&#xff09;架构&#xff0c;以高效、灵活和可扩展著称。其核心特性包括&#xff1a;高性能架构&#xff1a;基于 ZeroMQ 消息队列&#xff0c;支持大规模…