c语言内存分配区域:
在了解动态内存函数之前,我们需要了解c语言是在内存中如何存放变量的。
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。 - 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。 - 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
为什么存在动态内存
在我们没掌握动态内存之前,进行内存开辟的方式有:
#include<stdio.h>
int main()
{
int a=0;
char b-0;
int arr[10]={0};
return 0;
}
int a是在栈区上开辟4个字节的空间;char b在栈区上开辟了1个字节的空间;int arr[10]在栈区上开辟连续的40个字节的空间。
- 1.开辟空间的方式开辟的空间都是固定的。
- 2.数组在进行声明时要指定数组的长度。
当我们在程序运行时,当前空间太小,不够用怎么办?当我们确定不了需要开辟的空间怎么办?
struct stu
{
char name[20];
int age;
int grades[10];
}
此时,该学生的姓名过长或者过短,导致空间不足后者空间浪费应该怎么处理呢?
这就需要动态内存开辟空间了。
动态内存函数
malloc:
向内存申请了一块连续可用的空间,并且返回了指向该空间的起始地址的指针。
从上图中可以看出:
- 1.malloc接收的参数是size_t(无符号整数,单位是字节)类型的size。
- 2.malloc返回的类型是void*,所以我们要将malloc强转,还要使用使用指针来接收。
int* ptr=(int*)malloc(10*sizeof(int));
free:
用来释放动态申请的内存。
- 如果ptr指向的空间不是动态开辟的,那free的行为是不可预知的。
- 如果ptr是NULL,那么free将什么也不做。
int* ptr=(int*)malloc(10*sizeof(int));
free(ptr);
注意:
在我们使用完动态开辟的空间,一定要记得将动态开辟的空间进行释放,不然可能会导致内存泄漏哦
当我们使用完动态开辟的空间,并且释放掉之后,指向动态开辟的空间的起始得知的指针ptr就是一个野指针,我们要及时的将ptr指向NULL,防止程序出错。
int* ptr=(int*)malloc(10*sizeof(int));
free(ptr);
prt=NULL;
上边说到我们向内存申请了一块空间,会不会出现申请失败呢?
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(1000000000, sizeof(int));
if (p == NULL)
{
printf("申请内存失败!\n");
return;
}
printf("申请内存成功!\n");
free(p);
return 0;
}
从上面的例子中我们可以看出,内存申请的空间过大是会申请失败,所以我们需要去判断这次申请的空间是否成功。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
printf("申请内存失败!\n");
return;
}
printf("申请内存成功!\n");
for (int i = 0; i < 10; i++)
{
printf("%d\n",p[i]);
}
free(p);
p = NULL;
return 0;
}
从上图中可以看出,我们使用malloc申请的空间并没有进行初始化,而是存放的初始值。
正确的使用方法:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("内存开辟失败!\n");
return;
}
for (int i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p+i));
}
free(p);
p = NULL;
return 0;
}
注意:
- 如果malloc开辟空间成功,则返回指向该空间起始地址的指针。
- 如果malloc开辟空间失败,则返回NULL。
- 因为返回类型时void*,当我们使用时要注意将malloc返回值强转为我们需要的类型。
- 使用完动态开辟的空间后要及时的将动态开辟的的空间进行free(释放),防止该空间一直被占用(也许有人会说不free会咋样?当你的程序一直在运行,而你一直将malloc开辟的空间占据,这样下去总有一刻你的内存会满,导致你的程序奔溃)。
- 为了防止free后指向该malloc开辟的空间的指针变为野指针,我们要将该指针指向NULL。
calloc:
calloc和malloc相似,都是向内存申请了一块连续可用的空间,并且返回指向该空间起始地址的指针。
从上图可以看到:
- 1.我们calloc接收的参数有两个:size_t num,size_t size。
- 1.calloc将向内存申请num个size大小的空间。
2.calloc返回类型是void*,在使用时要将calloc强转。
int* ptr=calloc(10,sizeof(int));
既然是和malloc一样向内存中申请空间,那么就需要我们来判断该空间是否被成功申请,并且使用完该空间,要及时用free来释放,并且让指向申请空间的起始地址的指针指向NULL。
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
printf("内存开辟失败!\n");
return;
}
free(p);
p = NULL;
return 0;
}
那么calloc和malloc都是向内存申请空间,那他俩只需要一个就可以了,为什么还要calloc呢?他和malloc有什么区别?
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
printf("内存开辟失败!\n");
return;
}
for (int i = 0; i < 10; i++)
{
printf("%d\n", *(p + i));
}
free(p);
p = NULL;
return 0;
}
从上图中我们可以明显看出,malloc在成功申请到空间后,还将申请到的空间初始化为0。
当我们使用完calloc和malloc申请的空间太小,不够用或者申请的空间太大,我们就需要使用realloc函数来进行调整。
realloc:
当我们使用calloc和malloc申请的空间后,觉得申请的空间太小或者太大,我们可以使用realloc来灵活的调整我们申请的空间的大小。
从上图中可以看出:
- 1.realloc接收了两个参数:void* ptr和size_t size。
- 2.realloc需要指定需要调整空间的起始地址(ptr)和调整后的大小(size)。
- 3.realloc返回类型是void*,需要将返回的指向调整后的起始空间地址的指针。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = (int*)malloc(10*sizeof(int));
if (ptr == NULL)
{
return 1;
}
ptr = realloc(ptr, 15*sizeof(int));
if (ptr == NULL)
{
return 2;
}
free(ptr);
ptr = NULL;
return 0;
但我们在使用realloc调整空间时他会出现两种调整方法:
- 1.当原空间后有足够空间,直接在当前空间后继续追加,原来空间的数据不会发生改变。
- 2.当原空间后没有足够空间,realloc会在堆区上重新找一块合适的,连续的空间,将原空间中的数据拷贝到新空间中,返回指向新空间起始位置的指针。
例如:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = (int*)malloc(40);
if (ptr == NULL)
{
return 1;
}
for (int i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(ptr + i));
}
printf("\n");
int* tmp = realloc(ptr, 1000000);
if (ptr == NULL)
{
return 2;
}
ptr = tmp;
for (int i = 0; i < 10; i++)
{
printf("%d ", *(ptr + i));
}
free(ptr);
ptr = NULL;
return 0;
}
我们可以看到ptr地址是0x0132bff0,当我们调整的空间较大时,ptr调整后的空间就变为了0x00f0a040。原空间后的空间不够时,我们会在堆区上重新找一块合适的,连续的空间进行存放。
有人可能会问,当我们的原空间位置不够时,我们realloc会在堆区上重新找一块合适的连续的空间进行开辟,那我们可以用realloc开辟一块没有开辟过的空间呢?
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)realloc(NULL, 80);
if (p == NULL)
return;
for (int i = 0; i < 10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
拿realloc开辟一块未被开辟的空间是可以的,不过我们使用realloc开辟一块没有开辟的空间,一定要将空间的起始位置写为NULL。此时,realloc就和malloc的功能相同。
希望以上对您有所帮助!当然,如果文章出现错误,欢迎您在评论区或私信我指出哦~