内存分配方式(三种)
从静态存储区域分配
内存在程序编译的时候就已经分配好了,在程序运行期间这块内存都存在,如全局变量,static变量等。
在栈上分配
在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。如局部变量。
在堆上分配
堆上内存分配(动态内存分配)在程序运行的时候使用malloc或new申请任意大小的内存,使用完后需要使用free或delete释放内存,动态内存的生存周期由我们决定,使用非常灵活,但是存在问题也多。(使用时需要包含malloc.h或stdlib.h头文件)
常见内存管理错误
内存分配未成功
由于内存空间有限,使用如动态内存分配方式分配内存空间时,有可能分配不成功。在使用内存之前检查指针是否为NULL,如使用assert(p != NULL)检查。如果使用malloc或new申请内存,应使用if (p != NULL)进行检查。
内存分配成功未初始化
内存空间在申请到后,其缺省值是未知的,所以在空间申请到后,不要嫌麻烦,即便是赋零值也是不可省略的。
操作内存越界
在进行循环操作时避免多加一或多减一的操作,避免操作超出分配空间的范围。
未释放内存导致内存泄漏
使用动态内存分配时,程序中malloc/free,new/delete必须配对使用。
释放内存继续使用
栈内存指针在函数返回后依旧调用或者使用free/delete释放动态内存后依旧使用。
内存管理基本规则
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指针,是指向不明的指针。
“野指针”产生原因:
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指针。有如下两种处理方式:
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代码之前,最好放在启动代码中。
解决办法:在堆初始化代码中,将堆大小增加,一般0x400大小足够,如果不够的话,可根据实际调试情况进行增加。
看完本文有收获?请分享给更多人
关注「黑光技术」加星标,关注大数据+微服务