C语言空指针访问异常的深入理解
在昨天也是再次重启了网站服务。
今天就先随便记一下星期三软件测试课程上对一个例子的新的理解。
一、背景介绍
这个例子就是介绍一下测试报告是怎么写的。
课堂上使用的测试结果不符合预期的示例代码如下(程序名是hello):
可以看到代码很简单,就是读取参数然后分别处理。
现在测试的输入是在控制台输入 hello 不跟任何参数。
然后得到的结果是
测试结果不符合预期。
但是我这里要关注的是这个错误是如何产生的。
二、从C语言的角度进行分析
首先我注意到提示窗口说的是试图访问了一个不能读取的内存地址 0x00000000
而我们知道在C语言中 NULL 空指针默认就是指向0地址的
说明问题就是试图访问空指针指向的内存。
具体是在代码的 *argv[1] 这个位置
这次我再插叙一下C语言指针和数组的本质理解。
经过之前对于Rust的学习,我现在知道变量类型只是编译器提供的一种“假象”
内存当中只有地址和地址对应的值。
根据这种理解,我们再来看看 char* argv[] 是什么
首先按照C语言的语法,这是一个数组,数组中的元素是 char*
并且我认为在C语言中实际上并不存在“数组”这种类型
因为代表数组的变量并不像Rust一样代表的是一块连续的内存区域
而是代表的是数组首元素的地址。
但是由于在声明一个数组的时候只会有一块空间被编译器标记为数组元素,而不会像类型指针一样有一块空间用于真正存储首个元素的指针,所以数组和元素指针还是有些区别。
不管怎样,char* argv[] 在不输入任何额外参数时的内存布局应该如图所示。
所以 *argv[1] 就是第一个argv数组元素这个 char* 所指向的值,也就是 NULL 0x0
读取空指针就会触发异常。
三、从CPU和操作系统的角度进行分析
接下来到第二点,就是我在稍微深入学了操作系统之后注意到的,windows是怎么捕获到空指针访问异常然后弹出这个窗口的。
先回到应用程序,*argv[1] 本质上就是对某个地址的内存进行访问的操作,只不过试图访问的地址是0x0
然后,我们知道,CPU存在的虚拟内存机制再被操作系统启动后会自动在程序进行内存访问时将虚拟地址转换为物理地址后再去访问实际的内存。
所以,CPU在执行到访问内存的指令后,首先会去操作系统设置的当前程序的页表处查询要访问的虚拟地址对应的页表项,这是查询的是0x0虚拟地址对应的页表项,然后对于这个特殊的地址,操作系统在初始化程序的时候,就会将其初级页表中对应的页表项设置为不能被读写,并且也没有下级页表。
因此CPU便无法获取到对应的物理地址,触发异常。
最终异常被操作系统捕获,windows获取异常错误信息后弹出错误窗口进行提示。
所以,对应操作系统和CPU来说,并不存在什么“空指针访问异常”,所触发的只是试图访问不能被访问的内存的异常。
这也说明了为什么windows的提示窗不是直接说“试图访问空指针指向的地址”,而是说0x0对应的内存不能被“read”。
总结
以上就是对于上课时遇到的一段程序进行的联想,在博客中记录一下。
💬 评论区
暂无评论,来发表第一条吧。