什么是动态内存呢?
所谓的动态内存就是程序在运行的时候,根据实际的需求动态的分配和释放内存空间,都是在内存的堆区中进行的。
下面来介绍malloc函数↓
看到函数的参数传递一个无符号整形的数字(大小是字节)扩容成功返回一个void*的指针,指向扩容内存的起始地址,在接收的时候需要强制类型转换。 扩容失败就会返回一个空指针NULL。
给出代码实例↓
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
//malloc函数
int main()
{
//申请40个字节的空间存放10个整形
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));//扩容失败打印错误信息
return 1;
}
//存放1--10
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//打印
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放内存(必要)
free(p);
p = NULL;
return 0;
}
这条代码是在堆区开辟40个字节的空间存放1-10共10个整形
下面来介绍calloc函数↓
calloc函数的第一个参数开辟几个连续的空间,第二个参数是开辟的一个空间的大小(字节) 扩容成功返回一个void*的指针,指向扩容的空间的起始地址,同样也需要在接收的时候需要强制类型转换。扩容失败也会返回一个空指针NULL。
给出代码实例↓
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
//calloc函数
int main()
{
int* p = (int*)calloc(10, sizeof(int));//开辟10个大小为int型的内存 也一共40字节
if (p == NULL)
{
perror("calloc");
return 1;
}
//存放1--10
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
//打印
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//释放内存(必要)
free(p);
p = NULL;
return 0;
}
calloc与malloc函数的最明显的区别还是calloc开辟的空间会自动初始化而malloc不是
下面来介绍realloc函数↓
realloc函数是来调整上面两个函数所开辟的空间的开辟多了就可以释放掉多余的,开辟少了就可以增容。 函数的第一个参数是一个需要调整空间的起始地址,第二个参数是需要开辟空间的大小(字节) 注意:如果要扩容的话这里开辟的空间的大小要包含前面已经开辟的空间的大小。最后开辟失败也会返回一个空指针NULL。开辟成功会返回要被调整空间的起始地址。
realloc函数开辟空间有两种情况:
1.后面有足够的空间可以扩容直接在后边续上新的空间的地址,返回旧的起始地址。
2.当后面没有足够的空间可以扩容, realloc函数就会找一个满足空间大小的新的连续空间,把旧的空间的数据,拷贝新空间的前面位置,并且把旧的空间释放掉,同时返回新的空间的起始地址。
给出代码解释 同样是开辟10个整形的空间放1-10的数字
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
//realloc函数
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
//再加五个整形空间
int* ptr = (int*)realloc(p, 10 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
//继续使用空间
for (i = 5; i < 10; i++)
{
*(p + i) = i + 1;
}
//打印
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
这里就有一个疑问了为什么realloc函数返回的地址要用一个新的指针变量ptr接收 而不用原来的p , 但后面到了又将p的地址赋给了ptr 这不多此一举吗? 其实没有的,其实从代码中就能知道if语句判断ptr不为NULL,这一步就是以防万一realloc函数扩容失败的情况下返回NULL,如果用p来接收的话 p的内容就会被赋值为NULL,原来p指向的内容同时也会丢失。
最后是free函数
free函数是将开辟的内存释放掉 参数就是要被释放掉内存的地址 释放掉内存 指针变量会变成野指针 需要将其初始化为NULL。
有人就要问:程序结束的时候不是把所有的内存都释放掉吗, 这不是多此一举吗?
其实在我上述代码中会显得多此一举 但是代码如果有上百行千行,中间要扩容内存,如果没有及时释放掉就会造成很多问题。
以下是一些常见的问题:
1.内存泄漏:这是最直接的后果。每次动态分配的内存块如果不释放,随着程序运行,这些未释放的内存会不断积累。可用内存会越来越少,最终可能导致系统内存耗尽,程序因无法分配到足够内存而崩溃,或者其他程序因缺乏内存而无法正常运行。
2.性能下降:操作系统管理内存时,需要维护内存分配和释放的相关数据结构。大量未释放的内存会使这些数据结构变得复杂,增加内存管理的开销,从而影响程序的运行效率。例如,在频繁分配和不释放内存的情况下,内存碎片问题会更加严重,导致后续即使有足够的总内存,也可能无法分配出连续的足够大的内存块来满足需求。
3.程序稳定性降低:内存泄漏可能导致程序出现难以调试和定位的错误。由于可用内存不断减少,程序在运行过程中可能会因为内存不足而出现异常行为,如访问非法内存地址、程序崩溃等,而且这些错误可能在程序运行一段时间后才出现,增加了排查和修复的难度。