目录
1.为什么会存在动态内存分配
栈区:
空间开辟大小固定,数组在声明时必须指定数组长度,所需内存在编译时分配·
有些时候,我们需要的内存空间在程序运行时才知道,因此需要动态内存开辟。
2.动态内存分配函数
因为这些函数是C库函数提供的,所以头文件均是<stdlib.h>
a.malloc
void* malloc (size_t size);
函数功能:
- 向内存申请一块连续可用的空间,并返回指向这块空间的指针.
开辟成功,返回一个指向开辟好空间的指针。
开辟失败,返回一个NULL指针,因此malloc的返回值一定要做检查。(判断是否是空指针,对空指针解引用可是不允许的)
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定(强制类型转换)。
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
b.calloc
void* calloc (size_t num, size_t size);
函数功能:
- 开辟 num 个大小为 size 的一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别: calloc 会在返回地址之前把申请的空间的每个字节初始化为全0;
还是要注意在指针使用前要判空!!!
c.realloc
void* realloc (void* ptr, size_t size);
函数功能:
灵活调整内存大小,可大可小,“按需分配”
说明:
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
在调整原内存空间大小的基础上,还会将原来内存中的数据移动到 “新” 的空间。
注意:
若原有空间之后有足够的大的空间,直接扩容,原空间数据不发生变化
若原有空间之后没有足够大的空间,在堆上另找一个合适大小的空间,并把原来的内容复制到新空间,释放原空间
d.free
free(ptr);
ptr=NULL;
函数功能:
说白了就一“擦屁股”的----用于释放内存
释放ptr指向的空间,往往配合置空使用。
3.经典笔试题
胆大心细,想一想函数传参,形参实参,局部变量生命周期,方面的知识
问题:下面代码有没有问题?
No.1
- 考察函数传参,形参是实参的临时拷贝
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
有问题,并不能打印hello world
原因:
GetMemory函数传进去的是一个字符指针str,尽管p指向了一个100个字节的空间,但是对形参的修改并不能改变实参,对 str来说:空间并没有创建;所以也就不能完成拷贝;
改正方法:
传地址;&str,拿二级指针接收
No.2
- 考察局部变量的生命周期
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
有问题,不能打印hello world
原因:
p在传回来时,所指向的空间已经被销毁了,局部变量的生命周期只在自己的{}内,出了{},变量就被销毁了,也就导致了str成了一个野指针,自然也就不能打印hello world
改正方法:
static 修饰,让其具有全局变量的属性,以下两种均可
static char p[] = "hello world";
char static p[] = "hello world";
No.3
- 考察代码书写习惯
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
//if(!str)
//return;
strcpy(str, "hello");
printf(str);
//free(str);
//str=NULL;
}
确实能打印hello,但是代码有瑕疵:没有判断空间是否开辟成功,而且没有free,造成内存泄漏
说明:传的是str的地址,GetMemory()函数中对p进行解引用,就改变了str指向的空间,也就相当于这个时候str不再是一个NULL指针,而是一个指向大小为100个字节的空间的指针;
No.4
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);//
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
这就很明显了,空间都还没使用就free了,尽管str不是空指针,但它所指向的空间已经没了。
改正:先使用后free,再置空。完美!
4.柔性数组
用于结构体,规定结构体最后一个元素可以是未知大小的数组,即柔性数组。这个就可以配合动态内存分配函数,灵活运用空间(堆上)
下图为C/C++的内存区域划分
结构中的柔性数组成员前面必须至少有一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
实例:通讯录小程序中联系人信息的存储
5.常见的动态内存错误举例
1.对空指针解引用
会引起error,so指针判断是否为空指针很重要!!!
判断空指针方法
if语句,assert//断言,头文件assert.h,配合perror函数,若指针为空,直接输出错误信息
2.对动态内存开辟的空间进行越界访问
顾名思义,访问了开辟内存空间以外的范围, 比如指针解引用,数组下标访问
3.乱用free
释放了非动态内存开辟的空间
释放了动态内存分配的一部分空间
比如:
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
4.对同一块空间多次释放
“一次就够了,多了谁受得了”
5.忘记释放空间
这是个很不好的习惯,并且问题很严重,会造成内存泄漏。这次的空间使用过后不释放,下次使用又开辟新的空间,久而久之,你就会发现没内存可以用了!在平时的编译器下运行,程序员可以日常维护;但程序在服务器上运行时却少有人维护,后果就可想而知了。