目录
一、malloc(包含在头文件stdlib.h中)
1.malloc的定义
2.malloc的使用
二、calloc(包含在头文件stdlib.h中)
1.calloc的定义
2.calloc的使用
三、realloc(包含在头文件stdlib.h中)
1.realloc的定义
2.realloc的使用
3.realloc申请扩容时空间内存解析
在C语言中我们是否能按我们所需要的在系统中开辟想要大小的空间呢?当然没问题!本次博客来详细说一说一些常见的申请动态内存的函数(malloc、calloc、ralloc):
>该函数有一个参数size,通过此参数向堆区申请size大小字节的连续空间(该函数不对申请的空间的数值做初始化)
>返回一个指向该空间起始处的void*类型的指针,当空间申请失败时会返回一个空指针。
>如果size为0,malloc的行为是不确定的,取决于编译器。
当我们想好要申请要开辟的字节,通过malloc函数开辟成功时我们一般不用void*类型的指针来接收malloc的返回值。因为void*类型的指针不好解引用,也不方便管理开辟的空间。
例如:
int* p = (int*)malloc(40);
我们向内存的堆区空间申请了40个字节的空间(相当于在空间中开辟了10个int类型的变量),再强转型成int*类型的指针交给p来管理,这样我们可以一次访问4个字节的空间。当然我们在申请空间时也要根据具体情况来申请,想清楚用什么类型的指针管理最合适。
当然我们申请空间时也会出现申请失败的情况
例如:当我们申请开辟的空间过大时:
此时malloc会返回一个空指针,所以我们在申请完时要判断是否开辟空间成功,不能盲目进行下一步(可以通过strerror函数来报错):
在我们使用完申请的空间时,该空间因为在堆区不能自动释放(除非程序运行结束)所以在我们不使用该空间时要及时释放以免影响程序运行:
int main()
{
//申请
int* p = (int*)malloc(40);
//判断
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i; i < 10; i++)
{
*(p + i) = i;
}
//释放
free(p);
p = NULL;
return 0;
}
释放空间时我们使用函数free,此时交给函数free的指针应指向申请空间的起始地址,如果p被改动我们要找到申请空间的起始地址交给free函数否则就不能释放全部申请的空间造成浪费。完全释放完申请的空间时,p应置为空,以免形成野指针影响程序运行。
>该函数有两个size_t类型的参数num和size,意思是向堆区申请num个size字节大小的连续的空间,并返回一个指向起始开辟空间的void*类型的指针。
>该函数会对申请的空间的数值做初始化,将每个字节的空间的数值赋值为0。
>当空间申请失败时会返回一个空指针。
和malloc函数一样,我们一般也不用void*类型的指针来接收其返回值。
如:
第一个图是指针p的监视,第二个图是p指针的内存情况。从中我们可以很清楚的看到calloc函数对所开辟的空间都做了值为0的初始化(这意味着calloc比malloc函数更人性化,但是同时效率也相对更低)。
由于calloc函数和malloc函数基本相同的性质我们也要对所开辟的空间及时的做出检查和释放以免影响程序的正常进行:
int main()
{
//申请
int* p = (int*)calloc(10,sizeof(int));
//判断
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
int* pp = p;
for (i; i < 10; i++)
{
*p = i;
p++;
}
//释放
free(pp);
pp = NULL;
return 0;
}
在这里由于p指针在使用时被改变了,我们使用指针pp来指向开辟空间的起始地点,再将pp交给free函数来完全释放开辟的空间。
>realloc函数可以更改指向的动态空间的大小。
>通过void*类型的指针prt传给realloc函数将要改变空间的起始地址,通过size_t类型的参数size传给realloc函数此空间将要改变后的大小。
>申请成功后会返回一个void*类型的指向更改完成后起始空间位置的指针。
>当空间申请失败时会返回一个空指针。
>该函数扩容时结束后和传入空间地址不一定一致。
>当ptr传入空指针时,该函数直接申请开辟一个新的size字节大小的空间。
int main()
{
//申请
int* p = (int*)malloc(40);
//判断
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//使用
int i = 0;
for (i; i < 10; i++)
{
*(p + i) = i;
}
//增加空间
int* ptr = (int*)realloc(p, 80);
if (ptr != NULL)
{
p = ptr;
}
//释放
free(p);
p = NULL;
return 0;
}
观察以上例子:该段 代码没有直接拿p指针来直接接收realloc的返回指针,原因在于如果空间申请失败将会返回一个空指针,如果将空指针赋给p我们不仅不会拿到新的空间还会丢失原来的开辟的40个字节的空间。为了保险我们临时创建指针ptr来接收realloc的返回值,当它申请成功时再将其空间交给p。
在使用realloc函数时,该函数在扩容时的过程应该是什么样的呢?
我们模拟一下堆区内存的使用情况,在p处有足够的空间时realloc会从p指向的最后空间直接向后扩容,当要扩容时p向后扩展的空间不足了应该怎么办呢?realloc会找到一个足够大的空间在此处创建一个扩容后大小的空间,再将p所指向原来空间的数据一一对应拷贝到新的空间当中:
最后再释放p所指向的原来的空间,再将新空间的初始地址返回。
本次的博客又要结束啦,欢迎大家在评论区指出不足,886~