前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C/C++内存管理-学习笔记

C/C++内存管理-学习笔记

作者头像
黑光技术
修改2020-05-15 11:46:42
9920
修改2020-05-15 11:46:42
举报
文章被收录于专栏:黑光技术黑光技术

内存分配方式(三种)

从静态存储区域分配

内存在程序编译的时候就已经分配好了,在程序运行期间这块内存都存在,如全局变量,static变量等。

在栈上分配

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。如局部变量。

在堆上分配

堆上内存分配(动态内存分配)在程序运行的时候使用malloc或new申请任意大小的内存,使用完后需要使用free或delete释放内存,动态内存的生存周期由我们决定,使用非常灵活,但是存在问题也多。(使用时需要包含malloc.h或stdlib.h头文件)

常见内存管理错误

内存分配未成功

由于内存空间有限,使用如动态内存分配方式分配内存空间时,有可能分配不成功。在使用内存之前检查指针是否为NULL,如使用assert(p != NULL)检查。如果使用malloc或new申请内存,应使用if (p != NULL)进行检查。

内存分配成功未初始化

内存空间在申请到后,其缺省值是未知的,所以在空间申请到后,不要嫌麻烦,即便是赋零值也是不可省略的。

操作内存越界

在进行循环操作时避免多加一或多减一的操作,避免操作超出分配空间的范围。

未释放内存导致内存泄漏

使用动态内存分配时,程序中malloc/free,new/delete必须配对使用。

释放内存继续使用

栈内存指针在函数返回后依旧调用或者使用free/delete释放动态内存后依旧使用。

内存管理基本规则

  • 判断内存释放分配成功:使用malloc或new申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值为NULL的内存。
  • 不忘初始化:不要忘记为数组和动态内存赋初值。防止将未初始化的内存当有值使用。
  • 避免操作内存越界:避免数组或指针的下标越界。
  • 防止内存泄漏:动态内存的申请与释放必须配对,防止内存泄漏。
  • 防止野指针:使用fress或delete释放内存之后,理解将指针设置为NULL,防止产生”野指针”。

Sizeof与strlen对比

Sizeof可以求出数组容量,但是不能求出指针所指内存的容量。Sizeof指针只能求出指针变量的字节数。其中如果数组做函数的参数时,数组也是按指针处理的,即数组退化为指针:

Char x[] = “hello”

Char *p = x;

Sizeof(x) = 6;//包括字符串结束符’\0′

Sizeof(p) = 4;//与计算机位数有关

Void function(char x[10])

{

Sizeof(x) = 4;//与计算机位数有关

}

Strcpy-复制字符串

Strcmp-比较字符串

函数指针参数

如果函数的参数是一个指针,不要指望使用该指针去申请动态内存。

举例:

//定义

Void Get_Memory(char *Point, int Depth)

{

P = (char *)malloc(sizeof(char) * Depth);

}

//调用

Void Test(void)

{

Char *Str = NULL;

Get_Memory(Str, 100);//d调用后Str依旧为NULL

Strcpy(Str,”hello”);//错误

}

说明:指针作为函数参数时,只能传入指针内存储的地址,不能在子函数中修改改值,但是能够修改该指针所指向的内存区间的值,其道理和一般的变量参数是一样的。函数参数在子函数中使用,但是不能对其进行修改,在子函数调用时只是将改参数值传递给了子函数定义时的变量,子函数执行过程中只是修改该变量,而不是调用是传递的参数。举例如下:

//定义

Void function(char x);

//调用

Char Y = 0;

Function(Y);

说明:在调用function函数时,Y作为参数将其值传递给了function定义时的参数X,在Function执行过程中只是对X进行操作,而不是对Y。

如果非要使用指针参数去申请内存,那么应该使用”指向指针的指针”,举例如下:

//定义

Void Get_Memory(char **Point, int Depth)

{

*Point = (char *)malloc(sizeof(char) * num);

}

//调用

Void Test(void)

{

Char * Str = NULL;

Get_Memory(&Str, 100);//注意是&Str,是取了指针的地址,而不是指针指向的地址

Strcpy(Str, “hello”);

Free(Str);

}

说明:此方法中是使用指针的本身地址对其进行操作,修改了本身地址内的值,即将指针指向的地址值进行了修改。

函数返回值传递动态内存

举例说明:

//定义

Char *Get_Memory(int Depth)

{

Char *Point = (char *)malloc(sizeof(char) * num);

Return Point

}

//调用

Void Test(void)

{

Char * Str = NULL;

Str = Get_Memory(100);的地址

Strcpy(Str, “hello”);

Free(Str);

}

说明:以上方法比较方便,但是必须要保证malloc与free配对使用,避免内存溢出。在使用中定义需要使用malloc动态分配堆内内存,如果使用一下语句分配内存情况又不一样了,如下:

Char P[] = “Hello World”;//函数返回时P自动从栈内释放,所以函数返回报错

Char *P= “Hello World”;//常量字符串位于静态存储区,生命周期恒定不变,所以调用该子函数时,为”只读”状态,是一种错误的设计思想。

野指针问题

“野指针”不是NULL指针,是指向不明的指针。

“野指针”产生原因:

  • 指针变量未初始化:指针变量定义时指向是不明的,需要初始化。
  • 指针被Free/delete之后未设置为NULL:内存空间释放后指针变量并没被释放。
  • 指针操作越界:超出操作变量的范围。

Malloc/free与new/delete对比

Malloc/Free是C/C++的标准库函数

New/delete是C++的运算符

对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求,malloc/free是标准库函数,不是C++的运算符,不在编译器控制权限之内,不能够执行对象的构造函数和析构函数。

使用对比:

Malloc/free

1-malloc申请动态内存空间

2-初始化动态内存区

3-用户操作使用

4-清除工作

5-free释放内存空间

New/delete

1-new申请动态内存空间并初始化

2-用户操作使用

3-delete清楚并释放内存空间

由以上显而易见,不要使用malloc/free完成动态对象的内存管理,应该使用new/delete,但是对于内部数据类型两个是等价的。Malloc/free只是为了解决C语言动态内存分配的问题,在C++中既然有了new/delete,就不要为难自己使用前者了。

内存耗尽问题

如果动态申请分配的内存空间过大时将有可能导致内存分配不成功,即所谓的内存耗尽,使用malloc/new申请动态空间时将返回NULL指针。有如下两种处理方式:

  1. 判断指针是否为NULL,如果是则return或者exit(1)终止程序
  2. 为new和malloc设置异常处理函数

Visual c可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数,需要参考相关手册了。

对于动态内存申请导致耗尽的问题必须要处理,不能留给操作系统处理。如果操作系统使用了”虚拟内存”,那么申请动态内存是不成问题的,操作系统会自动从硬盘中分配的虚拟内存中进行分配,所以这将导致硬盘压力很大,所以软件必须要对内存分配添加处理,不然程序质量将非常糟糕。

Malloc/free用法

函数原型:

Void *malloc(size_t size);//参数为字节数

Void free(void * memblock);

调用:

Int *P = (int *)malloc(sizeof(int) * Length);

Free(P);

说明

在使用malloc函数申请空间时需要注意参数是字节数,所以最好使用sizeof计算系统中数据类型暂用的字节数进行计算,不同位数的计算机对数据类型的字节数是不一样的。另外需要注意的是将void *类型转换为自己需要的数据指针类型,并且通过指针是否为NULL判断是否申请内存成功。

New/delete用法

调用:

//基本数据类型

Int * Ponit = new int[Length];

Delete [] Point;//[]不能少,表示此为数组空间,如果丢失的话会有Length-1个空间不能够被释放

//对象类型

Obj *Objects = new Obj;//无参数构造函数

Obj *Objects = new Obj[100];//创建100个无参数构造函数

Obj *Objects = new Obj(1);;//创建一个有参数的构造函数,并且初值为1,有参数的只能单独创建

说明:

New方法分配基本数据类型的内存空间时比较简单,但是针对动态对象类型需要根据对象的构造函数而定,无参数的构造函数操作和基本类型的操作近似,但是对于动态对象类型需要注意参数,并且只能单独创建,不能创建为数组。Delete释放空间时要根据申请的类型进行释放,不是简单的指针,不然会导致部分空间不能释放。

KEIL中实现内存管理

Keil中使用C语言,所以关于内存分配处理除了静态存储区域分配、在栈上创建和VC中的使用一样外,需要注意在堆上的分配(动态内存分配),在堆上分配内存需要使用malloc和free函数管理内存,这两个函数在stdlib.h标准库头文件中。

在keil 中使用malloc()函数经常会遇到不正常的情况,通常表现为不能正确分配内存空间,或者只能分配很小的空间。出现这个问题的原因大概有三个:

1、所用的ARM芯片本身内存已经被其代码占用,所余空间不够malloc分配。

解决办法:A、释放其他代码浪费的RAM空间;B、扩容。

2、未进行堆的初始化:

在KEIL中使用malloc函数时,必需要对heap进行初始化,否则不能正常使用malloc。

解决办法:A、使用KEIL自带的启动代码,该汇编启动代码本身已经完成了对heap的初始化;(我们通常建立的工程都是使用KEIL自带的启动代码,或者在该基础上修改的代码,所以这个问题基本上不用考虑);B、自己编写heap初始化汇编代码,该初始化代码必须放在调用C代码之前,最好放在启动代码中。

  • 3、堆空间太小。

解决办法:在堆初始化代码中,将堆大小增加,一般0x400大小足够,如果不够的话,可根据实际调试情况进行增加。

看完本文有收获?请分享给更多人

关注「黑光技术」加星标,关注大数据+微服务

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 黑光技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档