为什么存在动态内存管理?
- 空间在不同的需求下,可能需要不断调整,导致代码的可扩展性下降。
- 有可能因为空间划分不合理,可能会导致空间浪费的问题。
- 一般在栈上,能一次有效分配的空间是有限的。
基于以上原因,故需要动态内存管理,动态内存管理带来的好处有:
- 让我们在程序运行期间,来决定开辟空间的大小。申请多少就给多少。
- 有效使用空间。不会造成空间上的浪费问题。
- 可申请的空间更多。堆空间>栈空间。
动态内存管理是在堆区申请空间,申请的空间是连续的。
在栈上,栈:自动申请,自动释放。用户不需要进行动态内存管理。
堆空间:需要自己申请空间,自己释放:free。
malloc函数
原型:void * malloc(size_t size);
malloc函数向内存申请一块连续可用的空间,并返回指向这个空间的指针。
- 如果开辟成功,返回一个指向这个地址的指针
- 如果开辟失败,返回一个NULL指针,所以返回值一定要做检查
- 返回值的类型为
void*
,可以按照我们的需求进行强转
free函数:专门用来做动态内存的释放和回收的。
原型:void free(void *ptr)
- 如果ptr不是动态内存开辟的,那么free函数这样的操作是没有定义的
- 如果ptr是NULL指针,函数就不会起作用
如果申请的空间未被free(忘了free):会造成内存泄漏。
内存泄漏会导致系统内存减小。如果程序退出了,内存泄漏的问题就自动恢复,内存归还给系统。
大部分时间都在运行的程序特别要注意内存泄漏的问题。
一个指针指向一段没有使用权限的空间,这样的指针叫做野指针(悬垂指针)。
int main()
{
int num = 5;
int *ptr = NULL;
ptr = (int *)malloc(sizeof(int)*num);
if (NULL == ptr)
{
exit(EXIT_FAILURE);
}
printf("%p\n", ptr);
free(ptr);
printf("%p\n", ptr);
return 0;
}
在free之后,ptr依然指向该地址,但是该指针没有权限对该地址进行操作。
calloc函数
void *calloc(size_t num,size_t size);
- 将num个大小为size的元素开辟一段空间,并将该空间每个节字初始化为0
与malloc函数相比:
malloc申请的空间,不会初始化。(可能快一些,因为少做了一些工作)
calloc申请的空间,会初始化。(按字节,初始化为0)
在申请空间的时候,一般所申请的实际大小是大于需要的内存空间大小的:
比如需要8个字节的空间,实际是大于8的。
为什么呢?
- 多出来的空间,不是给用户使用的,是给系统进行管理的。
- 将空间释放后,free不会对ptr置为空。释放的只是一个地址。但释放之后,不能使用,再使用会报错。释放的是指针和地址的对应关系。
动态内存管理的时候,要做到整体申请,整体释放。不要对指针指向进行更改。
realloc函数
void *realloc(void *ptr,size_t size)
- ptr是将要调整的内存地址
- size是调整的大小
- 返回值为调整之后的起始地址
如何调整?
realloc函数调整存在两种情况:
- 扩展:
直接扩展:原空间足够大,可直接进行扩展,返回值一样
重新开辟:(原空间不足),重新开辟空间,返回新地址
int main()
{
int num = 5;
int *ptr = NULL;
ptr = (int *)malloc(sizeof(int)*num);
if (NULL == ptr)
{
exit(EXIT_FAILURE);
}
printf("%p\n", ptr);
int *ret = realloc(ptr, 10000);
if (NULL != ret)
{
ptr = (int *)ret;
}
printf("%p\n", ptr);
free(ptr);
return 0;
}
2.收缩:直接进行收缩即可。
在使用realloc函数的时候,不能这样使用:直接讲重新申请的空间赋给旧的指针。
这样做会导致,如果开辟失败,返回会是NULL,原空间就会被置为NULL,这段空间就再也找不到了。指针也找不到了。堆空间就会丢失,导致内存泄漏。可用判定,来决定是否这样做。就如上述代码。
realloc函数执行之后,使用的是新申请的空间,包括free也是。而不再使用旧的空间,free也不释放旧的空间。
一个比较有意思的题:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void test()
{
char *str = NULL;
str = GetMemory();
printf(str);
}
GetMemory函数返回p的起始地址,调完的时候,就释放了。但是,p数组的地址,其数据仍然是存在的。
这就和计算机存储文件是一样的,文件删除,删除了这一块的使用权限,文件无效,但其数据仍然是存在的,数据恢复就是基于这样的性质进行的恢复的。
最后调用printf函数的时候,形成了新的栈帧结构,就会覆盖这个数据。故而这个代码输出的就是乱码。
在动态内存管理这块,要多注意:
- 申请的空间必须进行校验。(是否开辟成功)
- 使用完毕,一定要进行释放。(否则会造成内存泄漏)
- 不要越界访问。
- free释放的一定是动态内存开辟的空间,栈上的不可以。
- 不能将起始位置进行更改,也不能进行局部释放。
- 不要多次进行释放(第一次释放完之后,指针就对该空间无操作权限了,再释放就会出错)。