从内存分配谈起

来自:https://mp.weixin.qq.com/s/EyWKFRu1xryoHY386QUcuA

在步入正题之前,先声明一下,本公众号面向的是有一定基础的开发者,不过田老师会尽可能的用浅显易懂的语言把一些重要的、常用的技术点深入浅出的讲解出来,愿大家阅读、分享、共同学习,共同提高。

         言归正传,大家在平时的开发过程中,发生内存错误是件非常麻烦的事情,编译器不能自动发现这些错误,只有在程序运行时才会被捕捉到,而这些错误大多没有明显的症状,时隐时现,来无影,去无踪,在面对这些问题时,要想做到望闻诊切,然后药到病除,不如,让我们从头理一理内存的那些个事儿。

         内存是CPU和硬盘进行沟通的桥梁,CPU是处理器,是大脑和核心,内存和硬盘是存储器,受CPU的指挥。

         CPU工作的时候:

         1:需要从存储器把数据取出来。

         2:进行运算,要不停的对存储器读写。

         3:计算出结果,再返回到存储器进行存储。

如果硬盘够快的话,就不需要内存了,但硬盘太慢了,所以由硬盘担任1和3的工作,由内存分担硬盘2的工作,所以说内存就相当于硬盘和CPU之间的中转站。

         而我们一般写的程序经过预处理(主要是一些代码文本的替换工作)、编译(生成汇编代码)、链接(多个目标文件,库拼合的过程)、生成二进制的可执行文件保存在硬盘中,当运行可执行程序时,操作系统会把执行文件从硬盘拷贝到内存,之后CPU就从内存中读取执行文件的命令,CPU不断的一条指令一条指令的读取和执行。

         内存一般包括只读存储器(ROM),随机存储器(RAM),以及高速缓存(CACHE)。ROM是只读存储器,一般用于存放计算机的基本程序和数据,如BIOS,CPU对ROM是只读不存。

   而我们开发中提到的内存指的是计算机的随机存储器(RAM),一个由C/C++编译的程序占用的内存分为以下几个部分: 

(1)全局区(静态区):内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。

(2)栈区:由程序自动向操作系统申请分配以及回收,速度快,使用方便,但程序员无法控制。若分配失败,则提示栈溢出错误。在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区向地址减小的方向增长。

(3)堆区:即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。注意与数据结构中的堆不是一回事儿。堆区是向高地址扩展的,是不连续的。

(4)文字常量区:存放常量字符串的内存,程序结束后由系统释放。

(5)程序代码区:存放函数体的二进制代码。

内存分配如图所示,下是低地址,上是高地址:

环境变量和命令行参数

栈区

 

堆区

未初始化的全局区(静态区)

初始化的全局区(静态区)

代码段

    了解了内存分配的机理后,让我们看看常见的内存错误以及有哪些对策:

 1、在使用指针时,内存分配还没成功。
  编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。
2、内存分配虽然成功,但是尚未初始化就引用它。
  犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。
内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。
3、 内存分配成功并且已经初始化,但操作越过了内存的边界。
   例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

4、 忘记了释放内存,造成内存泄露。
   含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。
5、释放了内存却继续使用它。
有三种情况:
(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。
(2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。
(3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。

附:malloc和new的区别:

(1)new、delete是操作符,可以重载,只能在C++中使用。 

(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。 

(3)new 可以调用对象的构造函数,对应的delete调用相应的析构函数。 

(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数 

(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。

猜你喜欢

转载自blog.csdn.net/u013755520/article/details/91494140