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

这篇具有很好参考价值的文章主要介绍了解析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类的内存结构
解析c++空指针解引用奔溃,c++

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

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

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

编号 操作 访问符号 符号类型 符号地址 备注
Test* test = nullptr; - - - -
#1 Test copy = *test; test 指针类型局部变量 - -
#2 int value = test->value; Test::value 成员变量 0x8 value相对Test首地址的偏移量为8字节,因此地址为 0x0 + 8 = 0x8
#3 int stVAlue = test->stValue; Test::stValue 静态变量 固定地址 编译阶段分配好的地址,与指针test无关
#4 test->StFunction(); Test::StFunction 静态函数 固定地址 编译阶段分配好的地址,与指针test无关
#5 test->method_01(); Test::method_01 非虚成员函数 固定地址 编译阶段分配好的地址,与指针test无关
#6 test->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,因此会引起程序奔溃。文章来源地址https://www.toymoban.com/news/detail-788167.html

到了这里,关于解析c++空指针解引用奔溃的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】引用与指针

    专栏放在【 C++知识总结 】,会持续更新,期待支持 🌹 引用的概念 在C++中,引用的本质其实就是给一个已经存在的变量 ”起别名“ 。也就是说, 引用与它所引用的对象共用一块空间 。( 同一块空间的多个名字 ) 就比如说,李逵又叫黑旋风,而黑旋风就是指李逵本人,只

    2024年02月02日
    浏览(43)
  • C++的指针和引用

    C++中内存单元内容和地址 内存由很多的内存单元组成,这些内存单元用于存放各种类型数据; 计算机对内存的每个内存单元都进行了编号,这个编号就称为内存地址,地址决定了内存单元在内存中的位置; 记住这些内存单元地址不方便,因此C++语言的编译器让我们通过名字

    2024年02月06日
    浏览(46)
  • c++的引用和指针

    我们要清楚的知道,使用指针和引用都可以的传入函数的main函数的变量在局部函数改变值时,main函数里面相应的变量也会改变值。但他俩的方式不同。 我们先来说指针,指针传入局部参数时,他会在创建个局部指针变量,然后把传入的地址赋值给局部的指针变量,然后修改

    2024年02月09日
    浏览(48)
  • C++[第五章]--指针和引用

    引用就是别名,引用定义时必须初始化: int a; int b=a; //b即为a的别名 如果不是形参,必须初始化,引用某一变量 指针和c一样; this指针 在类的成员函数中使用,表示当前对象; C++11 新增了一种引用 类型 引用名 = 右值表达式; 引入右值引用如: 编译器允许我们为 num 左值建立

    2024年02月15日
    浏览(40)
  • C++拾遗(四)引用与指针

    引用和指针是两种不同的概念,尽管它们在某些方面有一些相似之处,但它们在功能和用途上是有所区别 引用:引用是别名,是对已存在变量的另一个称呼,一旦一个变量被引用,就不能再被引用其他变 量。 int a = 10; int ref = a;   这里, ref  是  a  的引用,它们引用的是同

    2024年01月25日
    浏览(55)
  • C++中const,指针和引用

    在线C/C++编译器,可以试着运行代码。 在C语言中,const修饰的量称为 常变量 (在编译过程中,const就是当成变量的编译生成指令的),不可以直接修改它的值,但是可以 通过地址进行修改其对应的值 。并且const修饰的变量可以不进行初始化,编译器最后默认赋值为0。 然而在

    2024年02月13日
    浏览(40)
  • 【C++初阶(三)】引用详解(对比指针)

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++初阶之路⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习排序知识   🔝🔝 C语言中有一利器: 指针 而C++中增加了另一利器: 引用 这两个板块的存在 极大了提升了C/C++的可用性! 本篇文章将给大家详细讲解引用 并

    2024年02月12日
    浏览(47)
  • C++中的引用及指针变量

    目录 1.1 C++中的引用 1.2 C++中的指针变量(pointer) 1.1 C++中的引用 C++中的引用(reference)是一种特殊的变量,它是某个已存在变量的另一个名字。引用变量与指针变量类似,但引用变量必须在声明时进行初始化,并且一旦引用变量与某个变量关联起来,就无法再与其他变量关

    2024年01月20日
    浏览(40)
  • 【C++那些事儿】函数重载与C++中的“指针“——引用

    君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C++ 游戏开发 Hello,米娜桑们,这里是君兮_,我之前看过一套书叫做《明朝那些事儿》,把本来枯燥的历史讲的生动有趣。而C++作为一门接近底层的语言,无疑是抽象且难度颇深的。我希望能努力把抽象繁多的知识讲的生

    2024年02月08日
    浏览(45)
  • C++智能指针学习——小谈引用计数

    目录 前言 控制块简介 共享控制块 引用计数与弱引用计数创建过程 __shared_ptr __shared_count _Sp_counted_base 弱引用计数增加过程 再谈共享控制块 __weak_count 引用计数增加过程 弱引用计数的减少过程 弱引用计数减为0 引用计数的减少过程 引用计数减为0 参考文章 本文结合源码讨论

    2024年04月08日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包