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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。