引言
无论你是一个刚接触计算机的小白,还是一个久经沙场的大牛,相信你一定曾被段错误这个问题困扰过,这些问题有些是有低级的错误引发,当然也很好修改,通常动动手指头就OK了,但有些错误则是十分隐晦,通常难已发现。如果让我来说段错误 其实大多数时候出现它并不是坏事,相同情况如果不出现段错误,那才是坏事。
为什么会出现段错误
系统发送 SIGSEGV 信号给出现段错误的程序,信号本质上是一种软件形式的异常,对信号的响应每种信号也并不相同,程序对 SIGSEGV 信号的默认处理则是 终止并转储内存(下面来解释为什么叫这个),所以我们的程序如果出现段错误后会立马结束
其实出现段错误简单来说就是访问到了不该访问的内存 这些内存不属于用户态的权限范围 比如内核的数据和代码段 所以系统禁止执行程序的进程或线程去访问或修改它们 想想看 如果我们随便一个程序就可以修改内核数据 那也太奇怪了。
出现段错误的主要场景
- scanf 没有加取址运算符 计算机会把变量实际值解释为十六进制的地址 运气好恰好解释的地址为内核的数据或者只读代码段 会出现段错误 从而发现错误,运气不好恰好解释为一个已经使用的数据块 那样就会污染数据,也不会把值赋给我们希望的变量
- 访问不存在的内存地址 虚拟内存实际上是成段分布的 一般来说Linux下的 x86 Inter core i7 64位机的虚拟地址为48位的虚拟内存空间 这是个非常大的数字 256TB 我们当然不会在进程刚被创建时就分配这么多的内存 而是在开始时仅分配需要的部分 我们先来看一张虚拟内存的分配图
自地址低位到高位我们总会留下许多的内存空隙 也就是还未被分配的内存 如果你感兴趣 可以打开shell进入
/proc 目录 这是个很棒的目录 可以让我们从用户态就可以看到内核态的数据结构的真面目 随便选择一个数字进入 然后执行以下命令
cat maps
我们就可以看到这个进程的虚拟内存的映射了 我们可以发现这并不是连续的 也就又印证了虚拟内存是成片的 回到我们的问题 不存在的内存就是未分配的 也就是没有办法把虚拟内训地址翻译成一个真实的物理地址(或者说在当前进程不合法) 那样当然会引发段错误
- 栈溢出 这个可以说是深有体会了 记得当时写ls命令-R遍历根目录的时候就会出现 现在看来也不难理解 我们回到刚刚那张内存的图片 栈位于最上面 高位地址为栈低 低位地址为栈顶 栈溢出就是越过定义的最大栈顶 那么现在地址在哪里呢? 这也就是栈溢出会发生段错误的原因
- 指针问题
在C语言编程的初始阶段指针是个过不去的坎 很容易一不小心指错了地方就引发段错误 我们可以按照上面的思路再来分析 到底是为什么呢 一般来说指针中开始时肯定是垃圾数据 如果我们为赋值或赋给了错误的值都会导致在虚拟地址中指向错误的地址 从而引发段错误
核心已转储
至于核心已转储 其实是因为在以前主存是用磁芯存储器来实现的,转储则是把代码和数据的内存映像再存储会磁盘 已保护数据