网站建设制作网络公司,郑州做互联网推广的,wordpress图片本地化插件,百度互联网营销顾问空指针解引用引起程序奔溃是c/c中最常见的稳定性错误之一。 显然并非所有使用空指针的语句都会导致奔溃#xff0c;那什么情况下使用空指针才会引起程序奔溃呢#xff1f;有一个判断标准#xff1a;判断空指针是否会导致访问非法内存的情况#xff0c;如果会导致访问非法内…空指针解引用引起程序奔溃是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因此会引起程序奔溃。