什么是内存不安全的情况
linux进程内存划分
一个进程在执行时,所占用的内存虚拟空间被划分为好几个区域,每个区域我们称为段。
常见的几个段:
- 代码段: 编译后机器码所在的区域。一般是只读的。
- bss段:存放为初始化的全局变量和静态变量的区域。
- 数据段: 存放初始化的全局变量和静态变量的区域。
- 函数调用栈:存放函数参数,局部变量的区域(在java里面就是栈帧)
- 堆:存放动态分配内存的区域。
堆和栈的区别
- 栈上保存的局部变量在退出当前作用域的时候会自动释放
- 堆上分配的空间没有作用域,需要手动释放。
- 栈上分配的空间大小是编译阶段就可以确定的。
- 栈有一个确定的最大长度,超过了就产生stackoverflow错误
- 堆的空间会大一些,堆上的内存耗尽了,就会产生outofmemory错误。
段错误
进程空间中的每个段通过硬件MMU映射到真正的物理空间;在这个映射过程中,我们还可以给不同的段设置不同的访问权限,比如代码段就是只能读不能写;进程在执行过程中,如果违反了这些权限,CPU会直接产生一个硬件异常;硬件异常会被操作系统内核处理,一般内核会向对应的进程发送一条信号;如果没有实现自己特殊的信号处理函数,默认情况下,这个进程会直接非正常退出;如果操作系统打开了core dump功能,在进程退出的时候操作系统会把它当时的内存状态、寄存器状态以及各种相关信息保存到一个文件中,供用户以后调试使用。
Rust的主要设计目标之一,是在不用自动垃圾回收机制的前提下避免产生segfault
内存安全
Memory safety is the state of being protected from various software bugsand security vulnerabilities when dealing with memory access, such as bufferoverflows and dangling pointers.
内存安全流派
- rust借鉴了cyclone,形成了通过编译器的内存所有者,生命周期,安全检查的方式从根本上解决内存安全的问题。
- 还有一派就是比c和cpp更细致的内存管理,当出现问题的时候能快速定位。(我没用过类似的语言,所以就不推荐了)
内存不安全的例子
- 空指针:解引用空指针是不安全的。这块地址空间一般是受保护的,对空指针解引用在大部分平台会产生segfault
- 野指针:野指针是未初始化的指针。它的值是取决于它这个位置以前留下的内容。所以是无法预料的,对它解引用可能会造成segfault,也可能不会,靠运气。
- 悬空指针:悬空指针指的是内存空间在被释放了之后,继续使用。它跟野指针类似,同样会读写已经不属于这个指针的内容。
- 使用未初始化的指针:不只是指针类型,任何一种类型不初始化就直接使用都是危险的,造成的后果我们完全无法预测。
- 非法释放:内存分配和释放要配对。如果对同一个指针释放两次,会制造出内存错误。如果指针并不是内存分配器返回的值,对其执行释放操作,也是危险的。
- 缓冲区溢出:指针访问越界了,结果也是类似于野指针,会读取或者修改临近内存空间的值,造成危险。
- 执行非法函数指针:如果一个函数指针不是准确地指向一个函数地址,那么调用这个函数指针会导致一段随机数据被当成指令来执行,是非常危险的。
- 数据竞争:在有并发的场景下,针对同一块内存同时读写,且没有同步措施。会导致和预期不同的结果。