一.为什么要有动态内存分配
我们已经掌握的内存开辟方式有:
int a = 10;
char arr[10] = { 0 };
但是上述的开辟空间的方式有两个特点:
- 空间开辟的大小是固定的
- 数组在申请的时候,必须指定数组的长度,数组的空间一旦确定了大小是不能调整的。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译方式就不能满足了。
C语言引入了动态内存开辟,这样我们自己可以根据情况自行申请和释放空间,就比较灵活。
动态内存是计算机中一种特殊的内存,用于存储在程序运行时创建和使用的数据。动态内存通过使用堆来分配和管理内存。与静态内存相比,动态内存的大小和生命周期不是在编译时确定的,而是在运行时根据程序的需求进行动态分配和释放。动态内存的使用可以提供更大的灵活性和效率,但也需要程序员负责手动管理内存的分配和释放,以避免内存泄漏和访问错误。
二.malloc和free
1.malloc函数
malloc函数是C语言提供的一种动态内存开辟函数。
void* malloc(size_t size);
这个函数所需的头文件是<stdlib.h>
malloc的m指的是内存(memory),alloc指的是allocate分配的意思。
这个函数的功能就是分配内存块。它的参数是size_t size.
malloc会分配size个字节的内存块,并且返回这个块的起始地址,由于不知道这个块所指向的数据的类型是什么,所以返回值的类型是 void *.
注:这个新分配的内存块的内容是不会被初始化的,也就是这个空间中的值是不确定的,也就是随机值。
//分配20个字节的空间,来存放5个整形
int* p = (int*)malloc(20);
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用空间
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
malloc函数的返回值的类型是void*,这样的类型是不能被解引用使用的,所以我们需要根据情况来对返回值进行类型转化,这里我需要它存放整形的变量,我就把它转化为int*类型的指针,并且用一个int*类型的变量p来接收。
malloc函数在向内存申请一块空间时,如果开辟成功,就会返回这个空间的指针,但是当我们开辟失败的时候,这个函数就会返回NULL指针,所以我们在使用malloc函数时,一定要对返回值进行检查。
一般来说我们在使用malloc函数的时候,我们是对我们所需要的空间的大小来进行申请的,这个大小不能是负数,一般情况下,也不能是0,如果是0,就没有必要申请了。如果我们非要传递0作为这个函数的参数,这个函数的返回值就是标准未定义的了(有可能是NULL,也可能不是),它会取决特定库来执行,不同的编译可能有区别。所以这样的情况下,这个空间是不能解引用的。
一般来说,我们申请空间是不会失败的,但是也有可能失败。
比如:
int* p;
while(1) {
p = (int*)malloc(INT_MAX);
if (p == NULL)
{
perror("malloc");
return 1;
}
如果我们一直申请空间,导致最后空间不足,就会申请失败。
最后就会打印空间不足。
2.free函数
C语言提供了另一个函数free,专门用来做动态内存的释放与回收的,函数原型如下:
void free(void* ptr);
这个函数的声明也在<stdlib.h>中
我们使用malloc函数申请了一块空间后,即使我们不再使用,如果我们不释放它,那么这块空间是不会被再次malloc的,而free函数就是将这些之前被malloc函数申请的空间释放掉,使其可以被再次malloc使用,不然如果我们一直malloc,空间就会不足了。
所以我们在上面写的代码均是有问题的,我们再使用过后,应该将这段空间释放掉。
int* p = (int*)malloc(4);
if (p == NULL){
perror("malloc");
return 0;
}
*p = 4;
printf("%d ",*p);
free(p);
p = NULL;
return 0;
在我们释放掉p所指向的空间后,p的值是不改变的,但是这块空间我们已经归还给操作系统了,我们无法使用,但是p还指向这个空间,这就是野指针,为了避免这样的情况我们就需要人为将p置为NULL;
如果ptr指针指向的不是动态内存开辟的空间,那么free函数造成的结果是未定义的。
观察这个代码是否有问题?
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL){
perror("malloc");
return 0;
}
for (int i = 0; i < 10; i++)
{
*p = i;
printf("%d ",*p);
p++;
}
free(p);
p = NULL;
return 0;
答案是有。
在我们使用free函数的时候有一个很重要的点,就是free函数的参数必须是这块空间的起始地址,而上面的代码的p已经不是我们申请的空间的起始地址了。所以我们对这个动态开辟的内存空间释放失败了。
这一点非常重要,free的参数所指向的必须是动态内存开辟的空间的起始地址。
如果我们提供的参数是NULL,那么free函数什么都不会做。
三.calloc和ralloc
1.calloc函数
C语言还提供了一个函数叫calloc,calloc函数也用于动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
这个函数的声明也是在<stdio.h>中
这个函数的功能和malloc函数很相似,作用是为num个大小为size字节的元素开辟一块空间,并且将这个空间的值初始化为0.
也就是说,第一个参数是元素个数,第二个参数是元素大小。开辟的总空间是num * size 个字节。
实际上calloc函数与malloc函数的主要区别就是对内存进行了初始化。
//使用malloc的情况
//int*p = (int*)malloc(5 * sizeof(int));
//if (p == NULL)
//{
// perror("malloc");
// return 1;
//}
//使用calloc函数
int* p = (int*)calloc(5, sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
如果size的值是0,那么这个函数的返回值是未定义的,(可能是NULL,也可能不是)。这样的返回值的指针是不能被解引用的。
2.realloc函数
顾名思义,realloc函数的作用就是重新分配内存空间。
realloc函数的出现让动态内存管理变大更加灵活。
有时候我们会发现我们之前申请的空间大小太小了,或者太大了造成空间浪费,这时候我们就可以使用realloc函数对这个内存大小做出一定的调整。
函数原型:
void* realloc (void* ptr, size_t size);
ptr指向的是动态内存开辟的空间的起始地址,size指的是我们对其进行调整后所需要的大小。
返回值是调整后的内存的起始地址。
在我们进行重新分配的过程中,是有两种情况的,如果这个空间后面的空间还没有被使用,那么就接着在后面开辟。
如果后面的空间已经被分配了,这时候不能接着后面开辟了,这时候这个函数就会寻找一个新的空间来满足这个大小,并且将原来内存中的数据移动到新的空间中。
由于存在上面的这两种情况,我们在使用这个函数时需要更加注意了。
int* ptr = (int*)malloc(100);
if (ptr == NULL)
{
perror("malloc");
}
//拓展内存
//代码1 - 直接将realloc的返回值放到ptr中
ptr = (int*)realloc(ptr, 1000);//这样可以吗?
//如果分配失败了怎么办?返回值是NULL.
//我们不仅没有分配到新的空间,而且还会丢失掉原来空间的地址,无法进行释放了。
//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
int* p = NULL;
p = realloc(ptr, 1000);
if (p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0;
在我们进行重新分配时,这个size可能比原来大也可能比原来小。
- 情况1:新的size小于原先的size.
如果这个内存空间被移动到了新的位置,那么原来内存块中的值也会跟着移动,但是由于size变小了,所以只会保留新的size这么大的内存空间的值。 - 情况2:新的size大于原先的size
如果这个内存空间被移动到了新的位置,那么原来内存空间的值也会跟着移动,但是size变大了,足以保存原先的值。而新分配的空间的值就是不确定的,随机值。
如果realloc函数的第一个参数是NULL,那么这个函数就和malloc函数一模一样,分配size个字节的空间并返回起始地址。
这个函数分配的空间位置变化了,原先的空间就会被自动free掉了,不需要我们人为free。
如果分配空间时失败了,那么这个函数会返回一个空指针,并且被参数ptr所指向的空间不会被释放掉,这个空间仍然有效,值也不会变,可以被访问。
对于上面三个函数来说,他们所分配的空间均要使用free函数来进行释放,如果没有释放,程序结束的时候操作系统也会回收,尽量要做到这三个函数和free函数成对出现。