C语言之总线错误、段错误、页错误、malloc、static

  • 当硬件告诉操作系统一个有问题的内存引用时,就会出现这两种错误。操作系统通过向出错的进程发送一个信号与之交流。进程收到 “bus error” 或 "segmentation fault"信号后将进行信息转储并终止。不过可以认为这些信号设置一个信号处理程序(signal handler),用于修改进程的缺省反应。

  • bus error (core dumped) 总线错误

    • CPU对进程引用内存的一些做法不满

    • 事实上,总线错误几乎都是由于未对齐的读或写引起的。之所以称为总线错误,是因为出现未对齐的内存访问的请求时,被堵塞的组件就是地址总线。

    • alignment(对齐)的意思就是:数据项只能存储在地址是数据项大小的整数倍的内存位置上。

    • 访问一个8字节的double数据时,地址只允许是8的整数倍。所以一个double数据可以存储于地址24、8008、32768,但不能存储于地址1006(因为它无法被8整除)。

    在这里插入图片描述

    • 上述总线错误,因为数组和int的联合确保数组a是按照int的4字节对齐的,所以“a+1”的地址肯定未按int对齐。然后我们试图往这个地址存储4个字节的数据,但这个访问只是按照单字节的char对齐,这就违反了规则

    • 上述的总线错误,a+1是char数组的第二个地址,是按照1个字节,所以你在这个1个字节的地址上要存储4个字节的int,未对齐,所以出现问题。

    • 总线错误也可能是由于引用一块物理上不存在的内存引起的。

  • segmentation fault (core dumped) 段错误

  • 直接原因

     解引用一个包含非法值的指针
     解引用一个空指针(从系统程序中返回空指针,并未经检查就开始使用)
     在未得到正确的权限时进行访问。例如:试图往一个只读的文本端存储值就会引起段错误。
     用完了堆栈或堆空间
    
  • 发生频率

     坏指针错误
         在指针赋值之前就用它来引用内存
         向库函数传送一个坏指针
         对指针进行释放之后再访问它的内容,可以修改free语句,在指针释放后再将它置位空值。
    

在这里插入图片描述

    改写(overwrite)错误
        越过数组边界写入数据
        在动态分配的内存两端之外写入数据,或改写一些堆管理数据结构

在这里插入图片描述

    指针释放引起的错误
        释放同一个内存块2次
        释放一块未曾使用malloc分配的内存
        释放仍在使用中的内存
        释放一个无效的指针

在这里插入图片描述

  • page fault 页错误

    • 定义

      • 当cpu执行进程的某个页面时,发现他要访问的页(虚拟地址的页)没有在物理内存中,而导致的中断(页错误)
    • 处理

      • 页错误发生后,操作系统去查询保存可执行文件和其进程虚拟空间映射关系的数据结构

      • 找到空页面所在的VMA虚拟内存区域。然后通过它VMA计算出空页面在可执行文件中偏移

      • 然后再物理内存中分配一个物理页面,并将该物理页和虚拟空间中虚拟页建立映射关系。

      • 最后将cpu控制权返还给进程,从刚才页错误的地方继续执行。

  • 虚拟内存区域(VMA: Virtual Memory Area):linux将进程虚拟空间中的一个段叫做虚拟内存区域。windows叫做虚拟段。

    在这里插入图片描述

  • 进程及页映射

     假设这个进程 add 是计算1+1 1+2 1+3 ……到 1+100000的值。
     由图可知,现在由于可执行文件到虚拟空间中只映射了VP0~VP7这8个页,并且其中的计算都在这些页中标识。
     并且VP0 VP1 VP7都已经映射到物理内存页的PP0 PP2 PP3处。
    
  • 映射关系和数据结构

     图中从左到右依次是:进程的可执行文件(存储在磁盘中) – 进程虚拟地址空间 – 物理内存地址空间
     **映射关系1(绿色)**:指的是可执行文件到虚拟地址空间的映射,其映射关系保持在操作系统数据结构1中。
     **映射关系2(紫色)**:指的是虚拟地址空间到物理内存页直接的映射,其映射关系保持在操作系统数据结构2中。
    
  • 页错误产生

     进程ADD执行1+1时,从操作系统保存的数据结构2中可以查到映射关系2,
     由于其进程虚拟空间页VP0已经映射到物理内存页PP0处,所以CPU执行,OK,结果是2。完成!
     进程ADD执行1+2时,从操作系统保存的数据结构2中可以查到映射关系2,
     由于其进程虚拟空间页VP1已经映射到物理内存页PP2处,所以CPU执行,OK,结果是3。完成!
     进程ADD执行1+3时,从操作系统保存的数据结构2中可以查到映射关系2,
     由于其进程虚拟空间页VP7已经映射到物理内存页PP3处,所以CPU执行,OK,结果是4。完成!
     进程ADD执行1+4时,从操作系统保存的数据结构2中查不到VP2与物理内存对于的映射关系。
     页错误产生了
    
  • 页错误处理

    • CPU控制权由进程ADD交给操作系统,操作系统去查询保存的数据结构1(保存的是可执行文件到虚拟地址空间的映射)

    • 通过该DP0页面所在的VMA虚拟内存区域,然后在VMA计算出页面在ELF(可执行文件)中的偏移,最后从物理内存中分配一个物理页面,并将该物理页面和进行虚拟地址建立映射关系。

    • 最后将CPU控制权交给ADD进程,从刚才页错误的地方继续执行,OK,结果是5,完成!

  • malloc

    • malloc就是memory allocate动态分配内存,malloc的出现时为了弥补静态内存分配的缺点,静态分配内存有如下缺点:

      • 传统的一维数组,如int a[5],使用传统的一维数组需要事先指定数组的长度,而且数组的长度必须是一个常量(宏定义的 常量)

      • 传统数组(静态分配),不能手动释放,只能等待系统释放,静态分配的变量在该函数内运行的时候有效,当静态分配的变量所在函数运行完之后,该内存会自动释放.

      • 静态分配的内存,是在栈中分配的,其实在C语言中的函数调用也是通过栈来实现的,栈这种数据结构的一个特点就是(先进后出),所以,在调用函数的时候,都是先压入栈中,然后,再从最上面的函数开始执行,最后,执行到main函数结束。动态分配通过malloc分配,是在堆中分配的,堆不是一种数据结构,它是一种排序方式,堆排序。

      • 传统数组的长度一旦定义之后,就不能更改,比如说,如果我有一个业务在这之前给分配的大小为100,但是,我现在由于业务数量的增长,原来的大小就无法满足。

      • 静态分配不能跨函数调用,就是无法在另一个函数中,来管理一个函数中的内存。静态分配,只在当前函数有效,当静态分配所在的函数运行完之后,该变量就不能被其他的函数所调用。

  • 使用

    • 使用malloc函数的时候,需要包含一个头文件#include <malloc.h>

    • malloc函数只接受一个形参如,int *p = (int )malloc(sizeof(int)).先来解释下这句话的含义,int
      p代表一个以int类型地址为内容的指针变量,p这个变量占4个字节(某些计算机),这个p变量是静态分配的一个变量。

    • 在某些计算机的前提下,指针变量所占的大小都是一样的,无论是char* 还是long*,因为,这些指针变量里面存放的是一个8位16进制的地址,所以占四个字节,当然这些都是在某些计算机的前提下,并不是所有的都是这样的。说道地址的话,就和计算机的地址总线有关,如果计算机的地址总线是32根,每根地址总线只有两种状态(1或0),32根地址线的话,如果全为1的话,刚好就是一个8位十六进制,一位十六进制等于四个二进制(2^4=16)。

    • 32根地址总线可以 表示210210210*22种状态,可以表示的最大内存为4G,也就是说32根地址总线(也就是四个字节
      的指针变量)最大可以表示4G内存

    • malloc函数会返回开辟空间的首地址,加(int *)的目的是让计算机知道,如何去划分这个开辟的空间,因为char、int
      、long这些类型的字节大小是不一样的,我们知道了首地址,还要知道是以几个字节为单元。

    • malloc开辟空间所返回的首地址是动态分配的。

在这里插入图片描述
在这里插入图片描述

  • static

    变量不加static修饰
    在这里插入图片描述
    在这里插入图片描述

    变量加static修饰
    在这里插入图片描述
    在这里插入图片描述

  • 总结:

    • 不加static修饰,函数或者代码块中的变量在函数或者代码块执行完毕后就直接回收销毁了,每次执行都会重新分配内存,每次都会销毁。

    • 加 static 修饰,函数或者代码块中的变量在函数或者代码块执行第一次初始化分配内存后,就算函数或者代码块执行完毕,该变量也不会被回收销毁,直到程序结束 static 变量才会被回收。

    • 当 static作用于函数定义时,或者用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性。外部链接属性变为内部链接属性,标识符的存储类型和作用域不受影响。也就是说变量或者函数只能在当前源文件中访问,不能在其他源文件中访问。

    • 当static 作用于代码块内部的变量声明时,static关键字用于修改变量的存储类型。从自动变量变为静态变量,变量的属性和作用域不受影响

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值