文章目录
动态内存管理篇
一、动态内存函数
动态内存函数需要包含头文件stdlib.h
与开辟静态内存空间(在栈区)不同,动态内存函数在堆区申请或释放内存空间。
malloc函数
malloc函数用来申请一块内存空间。
void* malloc(size_t size);
参数size表示要开辟的内存的字节数。要开辟多少个字节的内存,就传数字几。
返回类型为void*类型,因为malloc函数不清楚我们开辟这块空间要存储什么类型,所以使用时我们要根据需要对这个返回值进行强制类型转换,比如:
int* pi = (int*)malloc(40);
不过,malloc函数申请内存空间时可能会失败,失败后返回NULL,这时候我们要对malloc函数的返回值进行检查。
不要让malloc函数的参数为0,这种行为是未定义的,取决于编译器。
如果我们频繁、大量地开辟空间,会影响程序运行时间,还会导致堆区内存碎片化。
free函数
free函数用来释放动态内存空间,我们要养成释放多余空间的习惯,避免出现内存泄漏的问题。我们练习时执行的程序运行时间很短,程序结束时会自动释放(空间,而对于大的工程,需要不断地运行,这时候就需要free函数及时释放内存空间。
内存泄露
指在编程过程中,一块已分配的内存空间在使用完毕后没有及时释放,使得这块空间无法被再次使用,造成系统内存浪费和性能下降,甚至最终会导致系统崩溃的现象。
简单来说,就是我申请了一块空间,使用后没有释放,导致这块空间被占用又无法再被分配使用。
void free(void* ptr);
使用free函数时,传入的指针指向的必须是开辟的一块动态内存空间,free函数会把这块空间释放,不过释放后,传入的指针指向的地址还是之前的地址,释放后的空间不属于我们,所以我们要及时将传入的指针变量置空NULL
如果free函数的参数为NULL,那么free函数什么事都不做。
int pi = (int*)malloc(40);
free(pi);
pi = NULL;
calloc函数
calloc函数也可以动态内存开辟。
void* calloc(size_t num, size_t size);
功能为:为num个大小为size的元素开辟一块空间,同时将空间的每个字节初始化为0。
从结果上看,calloc函数和malloc函数的区别就是calloc函数在开辟空间时会初始化。
int* pi = (int*)calloc(10, sizeof(int));
if(pi == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
我们在选择使用malloc函数还是calloc函数时,只需要考虑是否想对开辟的空间初始化
这两个函数可以这样理解:calloc = malloc + memset
realloc函数
realloc函数用来对动态开辟内存大小进行调整。
void* realloc(void* ptr, size_t size);
- ptr就是要调整的动态内存的起始地址,指向要调整的内存空间。
- size是我们希望将ptr指向的内存空间调整成的内存大小,单位也是字节。
- realloc返回类型为void*,与malloc和calloc函数一样,我们需要进行强制类型转换。
int* pi = (int*)malloc(40);
if(pi == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
for(int i = 0; i < 10; i++)
{
*(pi + i) = i + 1;
}
int* pi_tmp = (int*)realloc(pi, 80);
前面我们没有介绍详细realloc函数的返回值,这里我们进行介绍。
我们假设要求realloc函数要将40个字节的动态内存空间扩容成80个字节的空间,这时候有两种情况:
- 当原内存空间后面的内存空间足够大。 realloc函数会在原空间后面接着再开辟40个内存空间,与原内存空间连续。这时候realloc返回的是原内存空间的起始地址。
- 当原内存空间后面的内存空间不够大。 为了避免把其他正在使用的内存空间的数据覆盖,realloc函数会在内存空间中重新找一块连续的80个字节的内存空间,并将原来内存空间存储的数据拷贝到新空间,之后自动释放原内存空间。这时候realloc函数返回的是新的内存空间的起始地址。
这时候出现了一个问题,我们用什么来接收realloc函数的返回值。既然我们使用realloc函数进行调整,,那么就不可避免地会出现调整失败地情况,比如,扩容失败。这时候,realloc函数会返回NULL,如果我们使用原来内存空间的指针变量来接收,结果是,我们既调整失败,又丢失了原来内存空间的地址。
怎么解决呢?
我们创建一个新的临时指针变量来接收这个返回值,判断是否为空指针,如果返回值不是空指针(说明调整成功),我们再把临时变量存储的地址传给原指针变量。
int* pi = (int*)malloc(40);
if(pi == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
for(int i = 0; i < 10; i++)
{
*(pi + i) = i + 1;
}
int* pi_tmp = (int*)realloc(pi, 80);
if(pi_tmp != NULL)
{
pi = pi_tmp;
}
//使用后记得释放
free(pi);
pi = NULL;
realloc函数除了调整功能外,还有别的用处,就是模拟malloc函数。
我们再来看一下realloc函数的格式:
void* calloc(size_t num, size_t size);
第一个参数要求传入一个指向要调整的内存空间的指针,如果我们传入NULL作为calloc的第一个参数,这时候,realloc相当于是malloc
例如,realloc(NULL, 40); 等价于 malloc(40);
二、常见动态内存错误
对NULL进行解引用操作
int* pi = (int*)malloc(1000000);
*pi = 10;//如果开辟失败,malloc函数会返回一个NULL
所以我们要对返回值进行判断:
int* pi = (int*)malloc(1000000);
if(pi == NULL)
{
return 1;
}
*p = 10;
free(pi);
pi = NULL;
越界访问动态内存空间
int* pi = (int*)malloc(40);
if(pi == NULL)
{
return 1;
}
for(int i = 0; i < 11; i++)//循环了11次,越界访问
{
*(p + i) = i + 1;
}
free(pi);
pi = NULL;
对非动态内存空间使用free函数
这种行为显然错误,我们要避免。
用free函数释放动态内存的一部分
int* pi = (int*)malloc(40);
pi++;
free(pi);//error,pi已经自增过了,不是指向完整的动态内存空间了,pi此时存放的地址是动态内存空间起始地址后4个字节的地址
对同一块动态内存多次释放
int* pi = (int*)malloc(40);
free(p);
free(p);//err,多次释放
内存泄漏
介绍free函数时一并介绍了,一定要记得及时释放动态开辟的内存空间。