c语言动态分配内存函数,c++语言中的动态内存分配原理和实现方法

  c语言动态分配内存函数,c++语言中的动态内存分配原理和实现方法

  【C语言】内存管理(动态内存分配栈堆静态存储区内存布局野指针)_ blog _语言动态内存分配详解

  目录1。动态内存分配1。动态内存分配的相关概念(1)动态内存分配(变量数组——内存别名 变量在编译阶段分配内存 除了编译器分配的内存,还需要额外的内存——动态内存)2 .动态内存分配的相关方法(1)相关方法介绍( malloc calloc realloc申请内存 自由返回内存 malloc申请内存,但不初始化值 calloc申请内存并初始化0 realloc重置申请的内存)(2) malloc函数(void * malloc(size _ t size);Size byte size 返回值void*需要强转换为指定类型 系统实际分配的内存略大于malloc 如果内存用完, 会返回NULL) (3) free函数( void free(void *ptr) 函数:释放malloc应用的动态空间 参数:void *ptr指针指向要释放的内存的第一个地址 返回值:无返回值)(4) calloc函数( void * Calloc (size _ tnemb,size _ t size) 函数:申请指定元素个数和指定元素大小的内存, 并将每个元素初始化为0 size_t nmemb参数:元素个数 size_t size参数:元素大小)(5) realloc函数( void *realloc(void *ptr,Size_t size) 函数:重新分配一个已分配但未释放的动态内存的大小 void *ptr参数:一个已有动态内存空间的首地址 size_t size参数:内存大小需要重新分配 ptr参数为空, 和malloc 功能一样使用新地址,旧地址ptr不能再用了)(6)代码示例(动态内存分配的简单例子)二。 堆栈静态存储区1。堆栈(1)堆栈相关概念(2)代码示例(函数调用的简单堆栈内存分析)(3)堆栈内存行为分析(图形分析版)2。堆(1)标题3 3。静态存储区(1)标题3。程序存储器布局1。程序运行前程序文件的布局(代码段数据段 bss段)(1)相关概念介绍(2)程序文件内存布局分析(2)程序运行后内存布局(栈堆映射文件数据[bss段数据段文本段]) (1)相关概念介绍(3)总结(4)野指针(程序BUG来源)1 .与通配符指针相关的概念(1)通配符指针介绍(2)通配符指针的三个来源(2)经典指针的错误分析(* *本节所有代码均为错误示例**) (1)非法内存操作(2)成功申请后内存未初始化(3)内存越界(4)内存泄漏(5)指针被多次释放(* * *谁申请就释放***) (6)已经释放的指针被使用。3.C语言避免指针错误的编程规范(1)申请内存后先判断空间(2)避免数组越界。注意数组的长度(3)谁申请动态内存就释放谁(4)释放后立即设置NULL。

  I .动态内存分配

  1.动态内存分配的相关概念

  (1)动态内存分配(变量数组-内存别名 变量在编译阶段分配内存 除了编译器分配的内存外还需要额外的内存-动态内存)

  动态内存分配:

  1.C语言操作与内存密切相关:C语言的所有操作都与内存有关;2.内存别名:变量(指针变量普通变量)和数组是内存中的别名;(1)内存分配的时机:在编译阶段,分配内存;(2)谁来分配内存:编译器来分配;(3)举例:如果定义数组时必须指定数组长度,则必须在编译阶段指定数组长度;3.动态内存分配的由来:程序运行时,除了编译器分配的一些内存外,可能还需要一些额外的内存来实现程序的逻辑,所以内存可以在程序中动态分配;

  2.动态内存分配的相关方法

  (1)相关方法介绍( malloc calloc realloc申请内存 自由返回内存 malloc申请内存,不初始化值 calloc申请内存并初始化0 realloc重置申请内存)

  动态内存分配方法:

  1.内存申请:使用malloc或calloc或realloc申请内存;2.返回内存:使用free返回请求的内存;3.内存来源:系统专门预留一块内存来响应程序的动态内存分配请求;4.内存分配相关函数:(1) malloc:简单申请指定字节大小的动态内存,不考虑内存中的值;(2) calloc:申请指定元素大小和个数的内存,将每个元素初始化为0;(3) realloc:可以重置应用的内存大小;#包含stdlib.h

  void * malloc(size _ t size);

  void free(void * ptr);

  void *calloc(size_t nmemb,size _ t size);

  void *realloc(void *ptr,size _ t size);

  (2) malloc函数(void malloc(size _ t size);Size byte size 返回值void需要强转换为指定类型 系统实际分配的内存略大于malloc 内存用完则返回NULL)

  malloc函数简介:

  void * malloc(size _ t size);1.功能:分配一个连续的内存,单位字节,没有具体的类型信息;2.函数分析:(1) size_t size参数:传入一个字节大小参数,size是要分配的内存大小;(2) void *返回值:返回一个void *指针,需要强制转换成指定类型的指针,指针指向内存的第一个地址;3.请求的内存大小:malloc实际请求的内存大小可能大于大小,这首先与编译器和平台有关。只知道这个,不要应用到编程上;4.应用失败:系统为程序保留一块内存,供程序运行时动态应用。当这个预留内存用完,用malloc申请时会返回NULL

  (3) free函数( void free(void *ptr) 函数:释放malloc应用的动态空间 参数:void *ptr指针指向要释放的第一个内存地址 返回值:无返回值)

  自由函数介绍:

  void free(void * ptr);1.功能:释放malloc函数应用的动态空间;2.函数解析:这个函数没有返回值;*( 1) void ptr参数:要释放内存的第一个地址;3.传入NULL参数:如果free方法传入NULL参数,则直接返回,不会出错;

  (4) calloc函数( Void * Calloc (size _ t nmeb,size _ t size) 函数:申请指定元素个数的内存并将每个元素初始化为0 size_t nmemb参数:元素个数 size_t size参数:元素大小)

  calloc函数简介:

  void *calloc(size_t nmemb,size _ t size);1.功能:比malloc高级,可以申请指定元素个数指定元素大小的内存;2.函数分析:(1)void * type的返回值:返回值是void *类型,需要转换成实际类型才能使用;(2) size_t nmemb参数:申请内存的元素个数;(3) size_t size参数:应用内存的元素大小;3.内存中值的初始化:calloc分配动态内存后,会将每个元素的值初始化为0;

  (5) realloc函数( void *realloc(void *ptr,Size_t size) 函数:重新分配已分配但未释放的动态内存的大小 void *ptr参数:已有动态内存空间的第一个地址 size_t size参数:内存大小需要重新分配ptr参数为空,函数同malloc 旧地址ptr不能再用)

  realloc函数介绍:

  void *realloc(void *ptr,size _ t size);1.功能:重新分配已分配和未释放的动态内存的大小;2.函数分析:(void * type的返回值:重分配后指针的第一个地址与参数ptr指向的地址相同,但需要使用返回的新地址,旧地址不能再用;*( 2) void ptr参数:现有动态内存空间的第一个地址;(3) size_t size参数:要分配的新内存大小;3.void *ptr参数为NULL:如果传递的ptr参数为NULL,那么函数执行的效果和malloc一样,直接分配一个新的动态内存,返回一个指向其第一个地址的指针;

  (6)代码示例(动态内存分配的简单示例)

  代码示例:

  1.代码:# includesdio.h

  #包含stdlib.h

  int main()

  {

  //1.使用malloc分配20个字节的内存,这些内存中的数据将保持不变。

  int * P1=(int *)malloc(sizeof(int)* 5);

  //2.使用calloc分配int类型的5个元素的内存,并将5个元素的值初始化为0。

  int* p2=(int*)calloc(5,sizeof(int));

  //3.将p1和p2指向的内存中的数据值打印为int类型。

  int I=0;

  for(I=0;i5;我)

  {

  printf(p1[%d]=%d,p2[%d]=%d\n ,I,p1[i],I,p2[I]);

  }

  //4.重新分配p1指向的内存,再分配10个数据;

  p1=(int*) realloc(p1,15);

  for(I=0;i 15我)

  {

  printf(p1[%d]=%d\n ,I,P1[I]);

  }

  返回0;

  }2.编译运行结果:

  二。堆栈静态存储区

  1.堆

  (1)堆栈相关概念

  堆栈简介:

  1.主要功能:维护程序的上下文信息,主要是局部变量和函数的存储;2.存储策略:后进先出法;

  堆栈功能的作用:

  1.那个函数依赖于栈:栈内存存储函数调用需要的所有信息:(1)栈存储函数参数:函数的参数会被依次放入栈并存储在栈内存中;(2)堆栈存储函数的返回地址:ebp指针指向返回地址,函数执行后跳转到返回地址继续执行下面的语句;(3)堆栈存储数据:局部变量存储在堆栈内存中;(4)堆栈保存函数调用的上下文:堆栈中保存了几个地址,包括返回地址、旧ebp地址、指向堆栈顶部地址的esp2.栈对于高级语言来说是必须的:如果没有栈,那么就没有函数,程序就退回到汇编代码的样子,从头到尾执行程序;

  函数栈存储的几个相关概念:

  1.esp指针:esp指针变量的地址并不重要,整个解释中也没有涉及到。重要的是esp所指向的值,这个值随着函数进出堆栈而不断变化;(1)堆栈:将esp上次指向的地址放入返回地址,然后esp指向新的栈顶;(2)弹出:获取返回地址中的地址,esp指向获取的地址(获取方式为ebp指针获取);2.ebp指针:ebp指针变量的地址不重要,在整个讲解过程中不涉及。重要的是ebp指向的值,这个值随着函数进出堆栈而不断变化;(1)堆栈:堆栈ebp指针指向的地址,ebp指向一个新的堆栈内存地址;(2)堆栈:ebp可以通过备份一个指针来获取返回地址(这个返回地址是esp指针使用的),然后ebp获取内存中的地址,然后ebp直接指向这个地址,也就是回到上一个函数的ebp地址;3.返回地址的函数:引导esp指针回到上一个函数的栈顶;4.ebp地址的作用:引导ebp指针退到上一个函数的EBP地址,获取esp的返回地址;5.初始地址:初始返回地址和旧ebp地址值是栈底地址;

  功能堆叠过程:

  1.参数堆栈:函数的参数存储在堆栈内存中;2.返回地址堆栈:每个函数都有一个返回地址,是当前esp指针指向的地址,也就是前一个函数的堆栈顶部。函数堆栈时,esp也指向这个地址,释放被弹出函数占用的堆栈空间;3.old esp stack: old esp是前一个esp指针指向的地址,存放在堆栈内存中,esp指针指向这个堆栈内存的第一个地址(这个堆栈内存就是存放old esp的堆栈内存);4.数据堆栈:寄存器和局部变量数据堆栈;5.esp指向栈顶:esp指针指向当前栈顶;

  功能发布流程:

  1.esp指针返回:根据ebp指针得到返回地址,esp直接指向这个返回地址;ebp获取返回位的方式:ebp指向返回地址的下一个指针,后退一个指针就可以得到ebp的指针,然后得到指针指向的内容作为返回地址;2.ebp指针返回:获取ebp指针指向的内存中的数据,也就是上一个ebp指向的内存地址值。当ebp指向这个地址值时,操作完成;3.释放堆栈空间:随着esp和ebp指针的返回,堆栈空间也被释放;4.继续执行函数体:从函数2返回函数1后,继续执行函数1的函数体;

  (2)代码示例(函数调用的简单堆栈内存分析)

  代码示例:

  1.代码:# includesdio.h

  无效函数1(整数I)

  {

  }

  int fun2(int i)

  {

  fun 1();

  返回I;

  }

  /*

  分析esp ebp指针进出栈的操作;

  程序开始执行,目前栈为空,栈底没有数据;

  注意:

  1.esp指针:esp指针变量的地址并不重要,整个解释也没有涉及到。重要的是esp所指向的值,这个值随着函数进出堆栈而不断变化;

  (1)堆栈:将esp上次指向的地址放入返回地址,然后esp指向新的栈顶;

  (2)弹出:获取返回地址中的地址,esp指向获取的地址(获取方式为ebp指针获取);

  2.ebp指针:ebp指针变量的地址不重要,在整个讲解过程中不涉及。重要的是ebp所指向的值,这个值随着函数进出堆栈而不断变化;

  (1)堆栈:堆栈ebp指针指向的地址,ebp指向一个新的堆栈内存地址;

  (2)堆栈:ebp可以通过备份一个指针来获取返回地址(这个返回地址是esp指针使用的),然后ebp获取内存中的地址,然后ebp直接指向这个地址,也就是回到上一个函数的ebp地址;

  3.返回地址的函数:引导esp指针回到上一个函数的栈顶;

  4.ebp地址的作用:引导ebp指针退到上一个函数的EBP地址,获取esp的返回地址;

  5.初始地址:初始返回地址和旧ebp地址值是栈底地址;

  1.主功能执行

  (1)将参数放入栈中:将参数放入栈中,主函数参数在栈底;

  (2)将返回地址放入栈中:然后将返回地址放入栈中,返回地址为栈底地址;

  (3)将ebp指针放入堆栈:将旧ebp指针放入堆栈,EBP指针指向存储旧ebp的地址address1,这是堆栈的底地址;

  (3)数据堆叠:(局部变量,寄存器值等。);

  (4) esp指向栈顶:esp指针指向栈顶(即数据后面第一个内存的地址),此时栈顶数据address 2;

  (5)数据汇总:主栈从下到上的数据:主参数-返回地址-旧ebp-数据

  (6)执行函数体:开始执行主函数并执行fun1函数的函数体。下面是堆栈中的内存变化:

  2.调用fun1函数,继续堆栈fun1函数内容:

  (1)参数堆叠:堆叠fun1参数。

  (2)返回地址堆栈:存储一个返回地址,即堆栈顶部的address2地址。当它返回时,将被ebp指针读回;

  (3) ebp指针堆栈:旧ebp指针指向的地址值被堆栈,指针指向address1地址,即t指向的位置

  堆栈内存存储上次ebp指针指向的地址address1,该内存存储address1的第一个地址是address3。

  ebp指针指向address3,即ebp指针变量存储address3的地址值,堆栈内存中的address3存储address1地址;

  (3)数据堆叠:存储数据(局部变量)

  (4) esp指向栈顶:esp指向栈顶

  (5)执行函数体:开始执行fun1函数体的内容,执行完后需要从堆栈返回;

  3.执行fun1功能后,堆叠展开和返回操作开始:

  (1)获取返回地址:返回地址存储在ebp的最后一个指针地址中,ebp指向返回地址的尾地址,

  Ebp可以通过回退一个指针位置得到返回地址,此时的返回地址是address2,上面已经介绍过了;

  (2) esp指针指向:esp指向address2,即将esp指针变量的值设置为address 2;

  EBP指针指向:

  获取上一个ebp指向的地址:当前ebp指向的内存存储上一个ebp指向的内存地址,获取这个地址;

  Ebp指向这个新获得的地址;

  (4)释放堆栈空间:释放当前地址和esp指针指向的后续地址;

  (5)执行主函数体:继续执行主函数体,然后执行fun2函数;

  4.执行fun2功能

  (1)参数堆叠:fun2函数的参数堆叠;

  (2)返回地址堆栈:esp指向的地址存储在返回地址中;

  (3) ebp地址堆栈:ebp指向的地址存放在堆栈内存中,ebp指向该段内存的第一个地址(即返回地址的尾地址);

  (4)数据堆叠:堆叠数据。

  (5) esp指向栈顶:esp指向数据的结束地址;

  (6)执行函数体:在执行fun2函数体时,发现fun1实际上是在fun2中被调用的,此时要将fun1函数重新放入堆栈中;

  5.fun1函数是堆叠的

  (1)参数堆叠:堆叠fun1参数。

  (2)返回地址堆栈:esp指向的返回地址存储在堆栈内存中;

  (3)将ebp地址放入堆栈:将旧的ebp地址放入堆栈,EBP指针指向堆栈内存的第一个地址(即返回地址的尾地址);

  (4)数据堆叠:局部变量和寄存器值堆叠;

  (5) esp指针指向:esp指针指向栈顶;

  (6)执行函数体:继续执行函数体。fun1函数执行后,函数执行完成,堆栈操作开始;

  6.fun1函数来自堆栈

  (1) esp指针返回:通过ebp读取前一个指针获得返回地址,esp指向返回地址,即前一个栈顶;

  (2) ebp指针返回:读取ebp指针指向的内存中的数据,这个数据是前一个ebp指针指向的地址值,ebp指向这个地址值;

  (3)释放栈空间:经过这两个操作,栈空间被释放;

  (4)执行函数体:fun1的执行从栈中释放后,继续执行fun2中的函数体,当发现fun2的函数体也完成时,开始fun2的释放;

  7.fun2函数从堆栈中出来

  (1) esp指针返回:通过ebp读取前一个指针获得返回地址,esp指向返回地址,即前一个栈顶;

  (2) ebp指针返回:读取ebp指针指向的内存中的数据,这个数据是前一个ebp指针指向的地址值,ebp指向这个地址值;

  (3)释放栈空间:经过这两个操作,栈空间被释放;

  (4)执行函数体:执行fun2并退出堆栈后,继续执行main中的函数体。如果主函数完成,esp和ebp都指向栈底;

  */

  int main()

  {

  fun 1(1);

  fun 2(1);

  返回0;

  }2.编译运行结果:如果没有输出结果,编译通过;

  (3)堆栈内存行为分析(图形分析版)

  分析的代码内容:

  #包含stdio.h

  无效函数1(整数I)

  {

  }

  int fun2(int i)

  {

  fun 1();

  返回I;

  }

  int main()

  {

  fun 1(1);

  fun 2(1);

  返回0;

  }代码堆栈内存行为操作的图形分析:

  1 .主函数执行:(1)参数栈:将参数放入栈中,主函数参数在栈底;

  (2)将返回地址放入堆栈:然后将返回地址放入sta

  (3)将ebp指针放入堆栈:将旧ebp指针放入堆栈,EBP指针指向存储旧ebp的地址address1,这是堆栈的底地址;

  (4)数据堆叠:(局部变量,寄存器值等。);

  (5) esp指向栈顶:esp指针指向栈顶(即数据后面第一个内存的地址),此时栈顶数据address 2;

  (6)数据汇总:主栈自下而上的数据:主参数-返回地址-旧ebp-数据(7)执行函数体:开始执行主函数的函数体,执行fun1函数。下面是堆栈中的内存变化:int main()

  {

  fun 1(1);

  fun 2(1);

  返回0;

  }2.调用fun1函数,继续堆栈fun1函数内容:(1)堆栈参数:堆栈fun1参数;

  (2)返回地址堆栈:存储一个返回地址,即堆栈顶部的address2地址。当它返回时,将被ebp指针读回;

  (3) ebp指针堆栈:将旧ebp指针指向的地址值进行堆栈,指针指向address1地址,即ebp指针上次指向的位置。

  堆栈内存存储上次ebp指针指向的地址address1,该内存存储address1的第一个地址是address3。

  ebp指针指向address3,即ebp指针变量存储address3的地址值,堆栈内存中的address3存储address1地址;

  (4)数据堆叠:存储数据(局部变量);

  (5) esp指向栈顶:esp指向栈顶;

  (6)执行函数体:开始执行fun1函数体的内容,执行完后需要从堆栈返回;无效函数1(整数I)

  {

  } 3.执行完}3.fun1函数后,启动栈展开返回操作:(1)获取返回地址:返回地址存储在ebp的前一个指针地址中,ebp指向返回地址的尾地址。

  Ebp可以通过回退一个指针位置得到返回地址,此时的返回地址是address2,上面已经介绍过了;(2) esp指针指向:esp指向address2,即将esp指针变量的值设置为address 2;

  EBP指针指向:

  获取上一个ebp指向的地址:当前ebp指向的内存存储上一个ebp指向的内存地址,获取这个地址;

  Ebp指向这个新获得的地址;

  (4)释放堆栈空间:释放当前地址和esp指针指向的后续地址;

  (5)执行主函数体:继续执行主函数体,然后执行fun2函数;int main()

  {

  fun 1(1);

  fun 2(1);

  返回0;

  }4.执行fun2函数:(1)叠加参数:fun2函数的叠加参数;

  (2)返回地址堆栈:esp指向的地址存储在返回地址中;

  (3) ebp地址堆栈:ebp指向的地址存放在堆栈内存中,ebp指向该段内存的第一个地址(即返回地址的尾地址);

  (4)数据叠加:叠加数据;

  (5) esp指向栈顶:esp指向数据的结束地址;

  (6)执行函数体:在执行fun2函数体时,发现fun1实际上是在fun2中被调用的,此时要将fun1函数重新放入堆栈中;int fun2(int i)

  {

  fun 1();

  返回I;

  }5.fun1函数堆叠:(1)参数堆叠:堆叠fun1参数;

  (2)返回地址堆栈:esp指向的返回地址存储在堆栈内存中;

  (3)将ebp地址放入堆栈:将旧的ebp地址放入堆栈,EBP指针指向堆栈内存的第一个地址(即返回地址的尾地址);

  (4)数据堆叠:局部变量和寄存器值堆叠;

  (5) esp指针指向:esp指针指向栈顶;

  (6)执行函数体:继续执行函数体。fun1函数执行后,函数执行完成,堆栈操作开始;无效函数1(整数I)

  {

  } 6.}6.fun1函数弹出堆栈:(1) esp指针返回:通过ebp读取前一个指针得到返回地址,esp指向返回地址,即前一个堆栈的顶部;

  (2) ebp指针返回:读取ebp指针指向的内存中的数据,这个数据是前一个ebp指针指向的地址值,ebp指向这个地址值;

  (3)释放栈空间:经过这两个操作,栈空间被释放;

  (4)执行函数体:fun1的执行从栈中释放后,继续执行fun2中的函数体,当发现fun的函数体

  }7.fun2函数出栈:(1) esp指针返回:通过ebp读取前一个指针得到返回地址,esp指向返回地址,即前一个栈顶;

  (2) ebp指针返回:读取ebp指针指向的内存中的数据,这个数据是前一个ebp指针指向的地址值,ebp指向这个地址值;

  (3)释放栈空间:经过这两个操作,栈空间被释放;

  (4)执行函数体:执行fun2并退出堆栈后,继续执行main中的函数体。如果主函数完成,esp和ebp都指向栈底;

  2.许多

  (1)标题3

  堆相关概念:

  1.堆栈的特点:当一个函数被执行时,它会被放入堆栈,而函数被执行后,函数堆栈会被释放,所以函数堆栈中的某些类型的数据无法被转移到函数外部;2.堆空间:malloc动态申请内存空间,这是操作系统保留的一块内存。这块内存就是堆,程序可以自由使用这块内存;3.堆的有效期:堆空间从申请开始生效,有效期到程序自愿释放。程序释放后,堆空间不可用;

  堆管理方法:

  1.自由链表方法;2.位图法;3.对象池方法;

  自由链表方法方案:

  1.自由链表图:表头-列表项-空;

  2.程序申请堆内存:int * p=(int *)malloc(sizeof(int));申请4字节堆空间,从自由链表中查找能满足要求的空间,找到满足要求的5字节空间。这里的5字节空间是直接分配给程序的,不需要给程序分配合适的内存。可以分配的内存大于请求的内存;

  3.程序释放堆内存:将P指向的内存插入空闲链表;

  3.静态存储区

  (1)标题3

  静态存储区的相关概念:

  1.静态存储区的内容:静态存储区用于存储程序的静态局部变量和全局变量;2.静态存储区大小:静态存储区大小可以在程序编译阶段确定,静态局部变量和所有变量可以加在一起;3.静态存储区的生命周期:静态存储区在程序开始运行时分配,程序运行后释放;4.静态局部变量:在程序运行过程中,静态局部变量总是被保留;

  总结:

  1.堆栈内存:主要存储与函数调用相关的信息;

  2.堆内存:用于程序申请动态内存并返回使用;

  3.静态存储区:用于存储程序中的全局变量和静态局部变量;

  三。程序存储器布局

  1.程序运行前程序文件的布局(代码段数据段 bss段)

  (1)相关概念介绍

  可执行程序文件内容:三段是程序文件的信息,编译后确定;

  1.文本部分(。textsection):存储代码内容,是编译时确定的,只能读,不能写;2.数据段:存储初始化的静态局部变量和全局变量,在编译阶段确定,可读写;3.bss部分(。BSS段):存储未初始化的静态局部变量和全局变量,可读写,程序开始执行时初始化为0;

  (2)分析程序文件的内存布局。

  简单程序的程序文件布局:

  1.示例代码:# includesdio.h

  //1.全局int类型变量,初始化并存储在数据段中。

  int global _ int=666

  //2.全局char类型变量,未初始化并存储在bss段中。

  char global _ char

  //3.fun1和fun2函数存储在文本段中。

  无效函数1(整数I)

  {

  }

  int fun2(int i)

  {

  fun 1();

  返回I;

  }

  int main()

  {

  //4.静态局部变量,已被初始化并存储在数据段中;

  static int static _ part _ int=888

  //5.尚未初始化的静态局部变量存储在bss部分;

  静态字符static _ part _ char

  //6.局部变量存储在文本段中。

  int part _ int=999

  char part _ char

  //7.函数和语句存储在文本部分。

  fun 1(1);

  fun 2(1);

  返回0;

  }2.代码分析图:

  2.程序运行后的内存布局(堆栈堆映射文件数据[bss段数据段文本段])

  (1)相关概念介绍

  运行后的内存布局:从高地址到低地址,顺序是栈-堆-bss段-数据段-文本段;

  1.堆栈:程序运行后分配堆栈内存,存储程序的函数信息;2.堆:堆栈内存分配后,分配堆内存来响应程序的动态内存申请;3.bss段:从程序文件映射到内存空间,存储未初始化的静态局部变量和全局变量,其值自动初始化为0;4 .数据段:从程序文件映射到内存空间,存储初始化的静态局部变量和全局变量;5 .文本段:从程序文件映射到内存空间,存储编写的程序代码;6.rodata段:程序中存储的常量信息只能读取,不能修改,如字符串常量、整数常量等。如果强行修改该段的值,执行时会报错;

  3.摘要

  程序存储摘要:

  1.静态存储区域:bss和。数据是静态存储区域;2.只读存储器区域。rodata段存储常数,是只读存储区;3.堆栈内存:局部变量存储在堆栈内存中;4.堆内存:使用malloc动态申请堆内存;5.代码段:代码存储在。文本段,函数的地址是代码段中的地址;

  函数调用过程:

  1.函数地址:函数地址对应程序内存空间中代码段的位置;2.函数堆栈:当一个函数被调用时,会在堆栈内存中建立一个函数调用的活动记录,如参数返回地址旧ebp地址数据等。3.对相关资源的访问:当一个函数被调用时,内存操作信息存储在代码段的函数中。在执行一个函数时,会根据esp栈顶的指针找到函数的局部变量等信息。当需要静态变量时,信息将从bss段或数据段中找到,当需要常量值时,信息将在rodata段中找到。

  四。野生指针(程序错误的来源)

  1.野生指针相关概念

  (1)野生指针的介绍

  野生指针相关概念:

  1.野指针的定义:野指针的指针变量中存储的地址值非法;2.指针合法指向:指针只能指向栈和堆中的地址,除了这两种情况,指针指向的其他地址都是非法的;3.空指针和野指针:空指针不容易出错,因为可以判断其指针地址为0;野指针地址不为0,但指向的内存不可用;4.无法判断野指针:目前在C语言中无法判断指针是否为野指针;

  (2)野指针的三个来源

  百搭指针来源:

  1.局部变量指针未初始化:局部指针变量在设置后未初始化;#包含stdio.h

  #包含字符串. h

  //1.定义一个包含字符串和int类型元素的结构。

  结构学生

  {

  char * name

  int age

  };

  int main()

  {

  //2.声明一个学生结构变量,但不初始化它,

  //结构中的两个元素都是随机值

  //初始化此局部变量需要malloc

  指导学生学习;

  //3.将“比尔盖茨”字符串写入stu.name指针指向的地址,

  //有事情要发生了,stu.name没有初始化,它的地址是一个随机值,

  //向随机地址写数据,任何情况都会发生,严重的情况会直接导致系统失效。

  //4.此时stu.name是一个通配符指针。

  strcpy(stu.name,比尔盖茨);

  stu.age=63

  返回0;

  }

  2.使用释放的指针:指针所指向的内存控件已经被free释放,使用后变成了野指针;如果指针没有分配,写了也没关系;如果把地址赋给一个程序,任意修改值会造成无法估量的后果;#包含stdio.h

  #包含malloc.h

  #包含字符串. h

  int main()

  {

  //1.创建一个字符串并为其分配空间

  char * str=(char *)malloc(3);

  //2.给字符串赋值,申请3个字节,但放入11个字符。

  //存在内存越界的风险

  strcpy(str,汉书梁);

  //3.打印字符串

  printf(%s\n ,str);

  //4.释放字符串空间

  免费(str);

  //5.再次打印,是空白的。

  printf(%s\n ,str);

  }

  3.指针指向的变量在使用前被销毁:如果指针指向的变量被销毁,这个变量所在的空间也可能被分配。如果修改了这个空间的内容,后果无法估计;#包含stdio.h

  //注意函数返回的局部变量。它们必须通过值传递,没有地址可以

  //注意,这个变量是一个局部变量,

  //函数执行后,变量所在的堆栈空间会被破坏。

  char * str= Hanshuliang

  返回字符串;

  }

  int main()

  {

  fun()函数返回的str的值是堆栈空间的值,

  //函数返回后释放值,

  //当前值已被销毁。

  char * str=fun();

  //打印的值可能是正确的

  printf(%s\n ,str);

  }

  2.经典指针错误分析(本节中的所有代码都是错误示例)

  (1)非法内存操作

  内存操作非法:主要问题是* *结构的指针成员,比如node 结构的指针没有初始化(分配了动态内存或者变量地址),或者* * * 初始化了,但是* * *超范围使用;

  1.结构成员指针未初始化:如果一个结构的成员中有一个指针,这个指针在使用时需要初始化。结构变量声明后,其成员变量值是随机的。如果指针值是随机的,那么这个指针的操作会产生未知的后果;示例:# includesdio.h

  //在结构中定义指针成员。当结构是局部变量时,指针成员int* ages需要手动初始化。

  培养学生

  {

  int*年龄;

  };

  int main()

  {

  //1.在函数中声明结构的一个局部变量,结构的成员不会自动初始化,这种情况下是一个随机值。

  结构学生stu1

  //2.遍历结构的指针成员并为其赋值,但指针尚未初始化。在随机空间上操作会导致未知错误。

  int I=0;

  for(I=0;i 10我)

  {

  stu 1 . ages[I]=0;

  }

  返回0;

  }2.结构成员初始化内存不足:初始化一个结构时,为其成员分配了空间,但使用的指针操作超过了分配的空间,因此使用超出的空间会造成无法计算的错误;示例:# includesdio.h

  #包含stdlib.h

  //在结构中定义指针成员。当结构是局部变量时,指针成员int* ages需要手动初始化。

  培养学生

  {

  int*年龄;

  };

  int main()

  {

  //1.在函数中声明结构的一个局部变量,结构的成员不会自动初始化,这种情况下是一个随机值。

  结构学生stu1

  //2.为结构变量中的ages指针分配内存空间并初始化它;

  stu1.ages=(int *)calloc(2,sizeof(int));

  //3.遍历结构的指针成员,给它赋一个值,这个值超出了它的2 * 4字节范围。8 ~ 11字节可能分配给其他应用。

  int I=0;

  for(I=0;i3;我)

  {

  stu 1 . ages[I]=0;

  }

  免费(stu 1 . ages);

  返回0;

  }

  (2)应用成功后内存未初始化。

  内存分配成功,没有初始化:内存中的随机值,如果对这个随机值进行操作,会有未知的后果;

  #包含stdio.h

  #包含stdlib.h

  //内存分配成功。它需要首先被初始化。该内存正在使用中。

  int main()

  {

  //1.定义一个字符串,并为它分配一个20字节的空间。

  char * str=(char *)malloc(20);

  //2.打印字符串,这里可能会有错误,因为内存没有初始化。

  //此时,里面的所有数据都是随机值。我不确定哪里有 \0 ,也就是字符串的结尾。

  //打印一个位置长度的str,显然不符合我们的需求

  printf(str);

  //3.释放内存

  免费(str);

  返回0;

  }

  (3)内存越界。

  内存越界分析:

  #包含stdio.h

  //数组退化:方法中的数组参数会退化成指针,也就是这个方法可以传入任何int*类型的数据。

  //无法确定数组大小:只有一个int* pointer变量,无法确定该数组的大小。

  //可能的错误:这里数组是按10个字节处理的。如果传入小于10字节的数组,可能会发生错误。

  void fun(int array[10])

  {

  int I=0;

  for(I=0;i 10我)

  {

  array[I]=I;

  printf(%d\n ,array[I]);

  }

  }

  int main()

  {

  //1.定义一个大小为5的int类型数组,稍后将该数组传递给fun方法。

  int array[5];

  //2.将大小为5的int类型数组传入fun函数,fun函数会根据越界的int[10]类型给数组赋值。

  //如果你给一个未知的地址赋值,会有无法估量的后果

  fun(数组);

  返回0;

  }

  (4)内存泄漏

  内存泄漏:

  1.错误示例:# includesdio.h

  /*

  内存问题:这个函数有一个入口和两个出口。

  正常退出:处理比较完善,会释放内存;

  异常退出:临时机制,处于某种状态,处理不完全,内存泄漏。

  */

  void fun(无符号整数大小)

  {

  //申请一个内存空间

  int * p=(int *)malloc(size * sizeof(int));

  int I=0;

  //如果大小小于5,则不处理,直接返回

  //注意:这个位置,善后处理不好,没有释放内存。

  //如果大小小于5,暂时退出函数,P指针指向的内存不释放。

  //p指针是局部变量。函数执行后,局部变量消失,然后内存无法释放。

  如果(5号)

  {

  返回;

  }

  //处理前内存大于等于5。

  for(int I=0;I尺寸;我)

  {

  p[I]=I;

  printf(%d\

  n", p[i]);

   }

   //释放内存

   free(p);

  }

  int main()

  {

   fun(4);

   return 0;

  }2.正确示例:#include stdio.h

  /*

   内存问题 : 该函数有一个入口, 两个出口

   正常出口 : 处理的比较完善, 内存会释放;

   异常出口 : 临时机制, 出现某种状态, 没有处理完善, 出现了内存泄露

  */

  void fun(unsigned int size)

  {

   //申请一块内存空间

   int* p = (int*)malloc(size * sizeof(int));

   int i = 0;

   //将错误示例中的此处的出口取消即可解决内存泄露的问题

   if(size = 5)

   {

   //内存大于等于5以后才处理

   for(int i = 0; i size; i ++)

   {

   p[i] = i;

   printf("%d\n", p[i]);

   }

   }

   //释放内存

   free(p);

  }

  int main()

  {

   fun(4);

   return 0;

  }

  ( 5 ) 指针多次释放 (谁申请谁释放)

  指针被多次释放 :

  #include stdio.h

  #include stdlib.h

  /*

   内存问题 : 多次释放指针

   如果规避这种问题 : 动态内存 谁申请 谁释放

  */

  void fun(int* p, int size)

  {

   int i = 0;

   for(i = 0; i size; i ++)

   {

   p[i] = i;

   printf("%d\n", p[i]);

   }

   //释放内存

   // 注意这里 p 不是在本函数中申请的内存

   // 如果在其它位置再次释放内存, 就可能会出错

   free(p);

  }

  int main()

  {

   //申请内存

   int* p = (int*)malloc(3 * sizeof(int));

   //使用内存, 并在函数中释放内存

   fun(p, 3);

   //如果在此处释放一个已经释放的内存, 就会报错

   free(p);

   return 0;

  }

  ( 6 ) 使用已经释放的指针

  使用已经释放的指针 :

  #include stdio.h

  #include stdlib.h

  /*

   内存问题 : 使用已经释放的指针

   如果规避这种问题 : 动态内存 谁申请 谁释放

  */

  void fun(int* p, int size)

  {

   int i = 0;

   for(i = 0; i size; i ++)

   {

   p[i] = i;

   printf("%d\n", p[i]);

   }

   //释放内存

   // 注意这里 p 不是在本函数中申请的内存

   // 如果在其它位置再次释放内存, 就可能会出错

   free(p);

  }

  int main()

  {

   //申请内存

   int* p = (int*)malloc(3 * sizeof(int));

   int i = 0;

   //使用内存, 并在函数中释放内存

   fun(p, 3);

   //使用已经释放的指针

   //产生的后果无法估计

   for(i = 0; i = 2; i ++)

   {

   p[i] = i;

   }

   return 0;

  }

  3. C语言中避免指针错误的编程规范

  ( 1 ) 申请内存后先判空

  申请空间后先判断 : 使用 malloc 申请内存之后, 先检查返回值是否为 NULL, 防止使用 NULL 指针, 防止对 0 地址进行操作, 这样会破坏操作系统的内存区; 操作系统检测到程序使用 0 地址, 就会杀死本程序;

  #include stdio.h

  #include stdlib.h

  int main()

  {

   //申请内存

   int* p = (int*)malloc(3 * sizeof(int));

   //申请完内存后, 先判断是否申请成功, 在使用这段内存

   if(p != NULL){

   //执行相关操作

   }

   //释放内存

   free(p);

   return 0;

  }

  ( 2 ) 避免数组越界 注意数组长度

  避免数组越界 : 数组创建后, 一定要记住数组的长度, 防止数组越界, 推荐使用柔性数组;

  ( 3 ) 动态内存 谁申请 谁释放

  动态内存申请规范 : 动态内存的***申请操作*** 和 释放操作 一一对应匹配, 防止内存泄露和多次释放; 谁申请 谁 释放, 在哪个方法中申请, 就在哪个方法中释放 ;

  ( 4 ) 释放后立即置NULL

  指针释放后立即设置NULL : 在一个指针被 free() 掉以后, 马上将该指针设置为 NULL, 防止重复使用该指针;

   ©

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • c语言调用退出函数 c语言退出整个程序怎么写
  • c语言中怎么给函数初始化 c语言的初始化语句
  • c语言编写函数计算平均值 c语言求平均函数
  • 详解c语言中的字符串数组是什么,详解c语言中的字符串数组结构,详解C语言中的字符串数组
  • 表达式求值c++实现,c语言实现表达式求值
  • 看懂c语言基本语法,C语言详解,C语言的基本语法详解
  • 用c语言实现快速排序算法,排序算法设计与实现快速排序C语言,C语言实现快速排序算法实例
  • 深入解析c语言中函数指针的定义与使用方法,深入解析c语言中函数指针的定义与使用情况,深入解析C语言中函数指针的定义与使用
  • 描述E-R图,E-R图举例,关于C语言中E-R图的详解
  • 折半查找法C语言,折半查找算法(算法设计题)
  • 折半查找法C语言,c语言折半法查找数据,C语言实现折半查找法(二分法)
  • 扫雷小游戏c++代码设计,c语言扫雷游戏源代码,C语言实现扫雷小游戏详细代码
  • 怎样统计程序代码行数,C语言统计行数,C#程序员统计自己的代码行数
  • 基于c语言的贪吃蛇游戏程序设计,用c语言编写贪吃蛇游戏程序,C语言实现简单的贪吃蛇游戏
  • 图的两种遍历算法,图的遍历算法代码c语言,Python算法之图的遍历
  • 留言与评论(共有 条评论)
       
    验证码: