动态内存管理
我们在编写程序的过程中,为了完成相应的需求,经常需要开辟一些内存来存储数据。例如:
int a = 10;//在栈空间上开辟了4个字节的空间
char b[10] 0= {};//在栈空间上开辟了10个字节连续的空间
除了上述的开辟内存方法,本篇文章介绍几个动态开辟内存的函数。
1.malloc:
参数:size(要开辟空间的大小),单位是字节。
功能:向内存申请一块连续可用size个字节大小的空间。
返回值:返回一个指向这块空间的指针。
头文件:#include <stdlib.h>
注意:1.如果内存开辟成功,则返回一个指向这块空间的指针。
2.内存开辟失败,返回一个空指针。所以,当使用malloc函数时,它的返回值一定要做检查。
3.返回值的类型是void*,也就是说malloc函数只负责开辟空间,开辟好了以后怎么去使用就该我们自己决定了。
使用:
#include <stdio.h> #include <stdlib.h> int main() { int* ret = (int*)malloc(5*sizeof(int)); if (ret != NULL) { for (int i = 0; i < 5; i++) { *(ret + i) = 1; } } for (int i = 0; i < 5; i++) { printf("%d ",ret[i]); } return 0; }
这就是malloc函数的简单使用,但是上述的代码是有问题的,当我们使用完这块空间,结束程序的时候,应该进行动态内存的释放和回收。C语言中提供了一个函数free,它是专门完成这项任务的。
功能:释放内存块空间。
参数:指向这块空间的指针。
头文件:#include <stdlib.h>
注意:1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
3.当使用free函数释放和回收了上述空间后,也就是说这块空间我们已经没有权利去使用了,但是ptr还记录着这块刚刚释放掉空间的地址,这是不合理的。综上所述,在释放完一块空间后,应该把指针置为NULL。
4.有动态内存开辟,就要有内存释放和回收。这两个动作是要同时出现的,使用后面介绍的函数开辟空间,最后也要使用free函数释放空间。所以一定要成对使用,“绿茶和青梅,天生是一对”,千万不要残忍的拆散它们。
对于上述的程序而言,应该加上这样的代码。
free(ret); ret = NULL;
2.calloc
这个函数也是用来动态开辟内存的,和malloc不同的是,使用calloc函数会在返回地址之前把申请的空间的每个字节初始化为0。
参数:num:要分配元素的个数,size:每个元素的大小。
功能:为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
头文件:#include <stdlib.h>
使用:
#include <stdio.h> #include <stdlib.h> int main() { int* ptr = calloc(5,sizeof(int)); if (ptr != NULL) { for (int i = 0; i < 5; i++) { printf("%d ",ptr[i]); } } free(ptr); ptr = NULL; return 0; }
温馨提示:动态开辟空间后要记得释放空间哦!
3.realloc
使用上述的两个函数开辟空间时,经常碰见的问题就是,内存开辟大了浪费空间,开辟小了不够使用。基于这种情况下,realloc函数的出现使得动态内存开辟管理更加的灵活。
参数:指向需要更改空间的指针,更改后的大小。
功能:调整ptr指向的空间大小。
头文件:#include <stdlib.h>
返回值:指向重新分配好的内存的指针,它可能与ptr相同,也可能是新的位置。
解释:
1.返回ptr:原有空间之后有足够大的空间,要扩展内存就直接在原有内存之后追加空间,原来空间的数据不发生任何变化。
红色部分表示被占用的空间。
2.返回新的位置:出现这种情况的原因是在原有空间之后没有足够大的空间。这时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间。把之前的数据拷贝过来,函数返回的是一个新的内存地址。
使用:
#include <stdio.h> #include <stdlib.h> int main() { int i = 0; int* ret = (int*)malloc(5*sizeof(int)); if (ret != NULL) { for ( i = 0; i < 5; i++) { *(ret + i) = i; } } for (int n = 0; n < 6; n++) { printf("%d ", ret[n]); } printf("\n"); //realloc扩展空间 int* ptr = (int*)realloc(ret,10*sizeof(int)); if (ptr != NULL) { //realloc函数返回的可能是一个新的内存地址。 ret = ptr; for (int j = i; j < 10; j++) { *(ret + j) = j; } } for (int n = 0;n<10;n++) { printf("%d ",ret[n]); } //释放空间 free(ret); ret = NULL; return 0; }先使用malloc开辟5*sizeof(int)大小的空间,我们为向开辟的空间中放入0-4的值,但是突发奇想,想要打印六个空间的数字,这时就用realloc来扩展空间,扩展后的空间大小是10*sizeof(int),向扩展后的空间放入5-9。打印出来看一下效果:
4.练习题
习题1.
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf("%s",str); } int main() { Test(); return 0; }请问运行Test 函数会有什么样的结果?这段代码存在着什么问题?
分析:
该程序的目的是:动态开辟一块空间,把一个字符串拷贝到这块空间中。
调用Test函数,Test函数中,把str的值传给GetMemory,GetMemory的形参是实参的一份临时拷贝,str不会随着p的改变而改变。所以当p指向动态开辟的100个字节的空间时,str仍然是NULL。当把一个字符串通过strcpy函数拷贝到NULL中时,代码就已经崩溃了。而且p是一个局部变量,出了GetMemory就会销毁,这也就是说GetMemory函数调用完后,在也找不到刚刚开辟的空间。改错:传参给GetMemory时,传str的地址过去。
void GetMemory(char** p) { *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }
习题2
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }请问运行Test 函数会有什么样的结果?这段代码存在着什么问题?
分析:
调用GetMemory函数,返回字符串数组p的地址,str指向这块空间。 但是当想要打印的时候会出现错误,因为char p[ ] = "hello word" ;在栈空间上开辟了一块连续的空间,但是出了GetMemory函数,刚刚开辟的空间就被销毁了,返回的地址赋给str,str确实记住了这个地址,但是没有访问这块空间的权限。所以当我们想要打印的时候,str指向的位置是不可知的,str就是一个野指针。
参数:size(要开辟空间的大小),单位是字节。







调用GetMemory函数,返回字符串数组p的地址,str指向这块空间。 但是当想要打印的时候会出现错误,因为char p[ ] = "hello word" ;在栈空间上开辟了一块连续的空间,但是出了GetMemory函数,刚刚开辟的空间就被销毁了,返回的地址赋给str,str确实记住了这个地址,但是没有访问这块空间的权限。所以当我们想要打印的时候,str指向的位置是不可知的,str就是一个野指针。