C++内存管理超详细分析

C++内存管理超详细分析 一、内存在计算机中每个应用程序之间的内存是相互独立的通常情况下应用程序 A 并不能访问应用程序 B当然一些特殊技巧可以访问但此文并不详细进行说明。例如在计算机中一个视频播放程序与一个浏览器程序它们的内存并不能访问每个程序所拥有的内存是分区进行管理的。在计算机系统中运行程序 A 将会在内存中开辟程序 A 的内存区域 1运行程序 B 将会在内存中开辟程序 B 的内存区域 2内存区域 1 与内存区域 2 之间逻辑分隔。1.1 内存四区在程序 A 开辟的内存区域 1 会被分为几个区域这就是内存四区内存四区分为栈区、堆区、数据区与代码区。栈区指的是存储一些临时变量的区域临时变量包括了局部变量、返回值、参数、返回地址等当这些变量超出了当前作用域时将会自动弹出。该栈的最大存储是有大小的该值固定超过该大小将会造成栈溢出。堆区指的是一个比较大的内存空间主要用于对动态内存的分配在程序开发中一般是开发人员进行分配与释放若在程序结束时都未释放系统将会自动进行回收。数据区指的是主要存放全局变量、常量和静态变量的区域数据区又可以进行划分分为全局区与静态区。全局变量与静态变量将会存放至该区域。代码区就比较好理解了主要是存储可执行代码该区域的属性是只读的。1.2 使用代码证实内存四区的底层结构由于栈区与堆区的底层结构比较直观的表现在此使用代码只演示这两个概念。首先查看代码观察栈区的内存地址分配情况12345678#includestdio.hintmain(){inta 0;intb 0;charc0;printf(变量a的地址是%d\n变量b的地址是%d\n变量c的地址是%d\n, a, b, c);}运行结果为我们可以观察到变量 a 的地址是 2293324 变量 b 的地址是 2293320由于 int 的数据大小为 4 所以两者之间间隔为 4再查看变量 c我们发现变量 c 的地址为 2293319与变量 b 的地址 2293324 间隔 1因为 c 的数据类型为 char类型大小为 1。在此我们观察发现明明我创建变量的时候顺序是 a 到 b 再到 c为什么它们之间的地址不是增加而是减少呢那是因为栈区的一种数据存储结构为先进后出如图首先栈的顶部为地址的“最小”索引随后往下依次增大但是由于堆栈的特殊存储结构我们将变量 a 先进行存储那么它的一个索引地址将会是最大的随后依次减少第二次存储的值是 b该值的地址索引比 a 小由于 int 的数据大小为 4所以在 a 地址为 2293324 的基础上往上减少 4 为 2293320在存储 c 的时候为 char大小为 1则地址为 2293319。由于 a、b、c 三个变量同属于一个栈内所以它们地址的索引是连续性的那如果我创建一个静态变量将会如何在以上内容中说明了静态变量存储在静态区内我们现在就来证实一下12345678910#includestdio.hintmain(){inta 0;intb 0;charc0;staticintd 0;printf(变量a的地址是%d\n变量b的地址是%d\n变量c的地址是%d\n, a, b, c);printf(静态变量d的地址是%d\n, d);}运行结果如下以上代码中创建了一个变量 d变量 d 为静态变量运行代码后从结果上得知静态变量 d 的地址与一般变量 a、b、c 的地址并不存在连续他们两个的内存地址是分开的。那接下来在此建一个全局变量通过上述内容得知全局变量与静态变量都应该存储在静态区代码如下123456789101112#includestdio.hinte 0;intmain(){inta 0;intb 0;charc0;staticintd 0;printf(变量a的地址是%d\n变量b的地址是%d\n变量c的地址是%d\n, a, b, c);printf(静态变量d的地址是%d\n, d);printf(全局变量e的地址是%d\n, e);}运行结果如下从以上运行结果中证实了上述内容的真实性并且也得到了一个知识点栈区、数据区都是使用栈结构对数据进行存储。在以上内容中还说明了一点栈的特性就是容量具有固定大小超过最大容量将会造成栈溢出。查看如下代码123456#includestdio.hintmain(){chararr_char[1024*1000000];arr_char[0] 0;}以上代码定义了一个字符数组 arr_char并且设置了大小为 1024*1000000设置该数据是方便查看大小随后在数组头部进行赋值。运行结果如下这是程序运行出错原因是造成了栈的溢出。在平常开发中若需要大容量的内存需要使用堆。堆并没有栈一样的结构也没有栈一样的先进后出。需要人为的对内存进行分配使用。代码如下123456789#includestdio.h#includestring.h#include malloc.hintmain(){char*p1 (char*)malloc(1024*1000000);strcpy(p1,这里是堆区);printf(%s\n, p1);}以上代码中使用了strcpy 往手动开辟的内存空间 p1 中传数据“这里是堆区”手动开辟空间使用 malloc传入申请开辟的空间大小 1024*1000000在栈中那么大的空间必定会造成栈溢出而堆本身就是大容量则不会出现该情况。随后输出开辟的内存中内容运行结果如下在此要注意p1是表示开辟的内存空间地址。二、malloc 和 free在 C 语言不是 C)中malloc 和 free 是系统提供的函数成对使用用于从堆中分配和释放内存。malloc 的全称是 memory allocation 译为“动态内存分配”。2.1 malloc 和 free 的使用在开辟堆空间时我们使用的函数为 mallocmalloc 在 C 语言中是用于申请内存空间malloc 函数的原型如下1void*malloc(size_tsize);在 malloc 函数中size 是表示需要申请的内存空间大小申请成功将会返回该内存空间的地址申请失败则会返回 NULL并且申请成功也不会自动进行初始化。细心的同学可能会发现该函数的返回值说明为 void *在这里 void * 并不指代某一种特定的类型而是说明该类型不确定通过接收的指针变量从而进行类型的转换。在分配内存时需要注意即时在程序关闭时系统会自动回收该手动申请的内存 但也要进行手动的释放保证内存能够在不需要时返回至堆空间使内存能够合理的分配使用。释放空间使用 free 函数函数原型如下1voidfree(void*ptr);free 函数的返回值为 void没有返回值接收的参数为使用 malloc 分配的内存空间指针。一个完整的堆内存申请与释放的例子如下1234567891011121314151617181920212223#includestdio.h#includestring.h#include malloc.hintmain() {intn, *p, i;printf(请输入一个任意长度的数字来分配空间:);scanf(%d, n);p (int*)malloc(n *sizeof(int));if(pNULL){printf(申请失败\n);return0;}else{printf(申请成功\n);}memset(p, 0, n *sizeof(int));//填充0//查看for(i 0; i n; i)printf(%d , p[i]);printf(\n);free(p);p NULL;return0;}以上代码中使用了 malloc 创建了一个由用户输入创建指定大小的内存判断了内存地址是否创建成功且使用了 memset 函数对该内存空间进行了填充值随后使用 for 循环进行了查看。最后使用了 free 释放了内存并且将 p 赋值 NULL这点需要主要不能使指针指向未知的地址要置于 NULL否则在之后的开发者会误以为是个正常的指针就有可能再通过指针去访问一些操作但是在这时该指针已经无用指向的内存也不知此时被如何使用这时若出现意外将会造成无法预估的后果甚至导致系统崩溃在 malloc 的使用中更需要需要。2.2 内存泄漏与安全使用实例与讲解内存泄漏是指在动态分配的内存中并没有释放内存或者一些原因造成了内存无法释放轻度则造成系统的内存资源浪费严重的导致整个系统崩溃等情况的发生。内存泄漏通常比较隐蔽且少量的内存泄漏发生不一定会发生无法承受的后果但由于该错误的积累将会造成整体系统的性能下降或系统崩溃。特别是在较为大型的系统中如何有效的防止内存泄漏等问题的出现变得尤为重要。例如一些长时间的程序若在运行之初有少量的内存泄漏的问题产生可能并未呈现但随着运行时间的增长、系统业务处理的增加将会累积出现内存泄漏这种情况这时极大的会造成不可预知的后果如整个系统的崩溃造成的损失将会难以承受。由此防止内存泄漏对于底层开发人员来说尤为重要。C 程序员在开发过程中不可避免的面对内存操作的问题特别是频繁的申请动态内存时会及其容易造成内存泄漏事故的发生。如申请了一块内存空间后未初始化便读其中的内容、间接申请动态内存但并没有进行释放、释放完一块动态申请的内存后继续引用该内存内容如上所述这种问题都是出现内存泄漏的原因往往这些原因由于过于隐蔽在测试时不一定会完全清楚将会导致在项目上线后的长时间运行下导致灾难性的后果发生。如下是一个在子函数中进行了内存空间的申请但是并未对其进行释放123456789101112#includestdio.h#includestring.h#include malloc.hvoidm() {char*p1;p1 malloc(100);printf(开始对内存进行泄漏...);}intmain() {m();return0;}如上代码中使用 malloc 申请了 100 个单位的内存空间后并没有进行释放。假设该 m 函数在当前系统中调用频繁那将会每次使用都将会造成 100 个单位的内存空间不会释放久而久之就会造成严重的后果。理应在 p1 使用完毕后添加 free 进行释放free(p1);以下示范一个读取文件时不规范的操作1234567891011121314#includestdio.h#includestring.h#include malloc.hintm(char*filename) {FILE* f;intkey;f fopen(filename,r);fscanf(f,%d, key);returnkey;}intmain() {m(number.txt);return0;}以上文件在读取时并没有进行 fclose这时将会产生多余的内存可能一次还好多次会增加成倍的内存可以使用循环进行调用之后在任务管理器中可查看该程序运行时所占的内存大小代码为1234567891011121314151617#includestdio.h#includestring.h#include malloc.hintm(char*filename) {FILE* f;intkey;f fopen(filename,r);fscanf(f,%d, key);returnkey;}intmain() {inti;for(i0;i500;i) {m(number.txt);}return0;}可查看添加循环后的程序与添加循环前的程序做内存占用的对比就可以发现两者之间添加了循环的代码将会成本增加占用容量。未被初始化的指针也会有可能造成内存泄漏的情况因为指针未初始化所指向不可控如12int*p;*p val;包括错误的释放内存空间123ppp;free(p);free(pp);释放后使用产生悬空指针。在申请了动态内存后使用指针指向了该内存使用完毕后我们通过 free 函数释放了申请的内存该内存将会允许其它程序进行申请但是我们使用过后的动态内存指针依旧指向着该地址假设其它程序下一秒申请了该区域内的内存地址并且进行了操作。当我依旧使用已 free 释放后的指针进行下一步的操作时或者所进行了一个计算那么将会造成的结果天差地别或者是其它灾难性后果。所以对于这些指针在生存期结束之后也要置为 null。查看一个示例由于 free 释放后依旧使用该指针造成的计算结果天差地别12345678910111213141516#includestdio.h#includestring.h#include malloc.hintm(char*freep) {intvalfreep[0];printf(2*freep:%d\n,val*2);free(freep);valfreep[0];printf(2*freep:%d\n,val*2);}intmain() {int*freep (int*)malloc(sizeof(int));freep[0]1;m(freep);return0;}以上代码使用 malloc 申请了一个内存后传值为 1在函数中首先使用 val 值接收 freep 的值将 val 乘 2之后释放 free重新赋值给 val最后使用 val 再次乘 2此时造成的结果出现了极大的改变而且最恐怖的是该错误很难发现隐蔽性很强但是造成的后顾难以承受。运行结果如下三、new 和 deleteC 中使用 new 和 delete 从堆中分配和释放内存new 和 delete 是运算符不是函数两者成对使用(后面说明为什么成对使用)。new/delete 除了分配内存和释放内存与 malloc/free还做更多的事情所有在 C 中不再使用 malloc/free 而使用 new/delete。