目录
1.为什么存在动态内存分配
2.动态内存函数的介绍
3.常见的动态内存错误
1.为什么存在动态内存分配
在了解之前我们可以先了解一下内存的几个区,如下图:
可以看到动态内存是在堆区上实现的,涉及动态内存分配的几个函数有malloc,calloc,realloc,free。
我们一般的内存开辟都是在栈区上的,比如一个整形变量在栈空间上开辟四个字节,一个整形数组在栈区开辟一块连续的空间,但是这种方法有两个特点:
1.空间开辟大小是固定的。
2.数组在申明的时候必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是这些情况,有时候我们需要的空间大小在程序运行的时候才能知道,这时候就只能试试动态内存开辟了。
2.动态内存函数介绍
1.malloc和free
malloc函数:void* malloc(size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
- 如果参数size为0malloc的行为是标准未定义的,取决于编译器。
free函数:void free(void* ptr);
free函数用来释放动态开辟的内存。
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数ptr是NULL指针,则函数什么事都不做。
malloc和free都声明在stdlib.h头文件中
举个例子:
#define _CRT_SECURE_NO_WARNINGS 1 #include#include int main() { //申请空间 int* p = (int*)malloc(40);//拿一个整形指针来维护后面的40个字节,向内存申请了40个字节的空间,把这个四十个字节的起始地址强制类型转换成int*之后赋值给p的指针 if (p == NULL) { return -1; } //开辟成功了 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } //释放空间 free(p);//在释放时不会主动把p指针变为空,所以要主动赋值为空,这样p才不会指向malloc指向的空间 p = NULL; return 0; }
可以看到开辟成功后地址都对应i的值发生了改变。
2.calloc
calloc函数:void* calloc(size_t num,size_t size);
- 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字初始化为0.
- 与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为0,而malloc不会进行初始化.
举个例子:
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%sn", strerror(errno));//开辟失败时报错,打印错误的信息
return -1;
}
//申请成功
int i;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));//打印10个0
}
//释放空间
free(p);
p = NULL;
return 0;
}
可以看到开辟成功时,打印了十个0,确实是把空间的每个字节初始化为0.
当我们申请空间过大开辟失败时会返回错误信息。
3.realloc
realloc函数:void* realloc(void* ptr,size_t size);
- realloc函数的出现让动态内存管理更加灵活。
- 有时候我们会发现过去申请的空间太小了或者太大了,为了合理使用内存,我们就可以使用realloc函数对动态开辟内存的大小进行调整。
realloc参数的解释:
- ptr是要调整的内存地址
- size调整之后新大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
- 开辟失败返回空指针。
- realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
当时情况1的时候,要扩展内存就直接原有内存之后直接追加空间,原来的数据不发生变化。
如下图所示,直接可以在后面追加空间
情况2:原有空间之后没有足够大的空间
当时情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另外找一个合适大小的连续空间来使用。这样函数返回的就是一个新的内存地址。
如下图所示:
代码如下:
int main()
{
//申请10个int的空间
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%sn", strerror(errno));//开辟失败时报错,打印错误的信息
return -1;
}
//申请成功
int i;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
//空间不多了,增加空间至20个int
int* ptr = (int*)realloc(p, 20 * sizeof(int));//定义新的指针接收地址
if (ptr != NULL)//由于开辟失败会返回空指针
{
p = ptr;//若开辟成功将ptr的值赋给p
}
else
{
return -1;
}
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
//打印20个,结果为0到19
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
3.常见的动态内存错误
1.对NULL指针的解引用操作
int main()
{
int* p = (int*)malloc(20);
//错误操作
//正确操作,对malloc的返回值进行一个判断,然后再进行赋值
if (p == NULL)
{
return -1;
}
*p = 0;
return 0;
}
2.对动态开辟空间的越界访问
int main()
{
int* p = (int*)malloc(200);
if (p == NULL)
{
return -1;
}
//使用
int i = 0;
//越界访问,申请的空间应该只有50个整形数据,会产生错误
for (i = 0; i < 80; i++)
{
*(p + i) = i;
}
//释放
free(p);
p = NULL;
return 0;
}
3.对非动态开辟内存使用free释放
int main()
{
int i = 0;
int* p = &i;
//p指向的空间在栈上,此时用free释放p的空间就会产生错误,释放了一个非堆上的空间
free(p);
return 0;
}
4.使用free释放一块动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return -1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*p++ = i;//p在++之后地址已经改变,起始位置已经不在开头了,此时进行释放空间就会产生错误
}
//释放
free(p);
p = NULL;
return 0;
}
5.对同一块动态内存多次释放
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
//释放
//两次释放时错误的
//正确写法
free(p);
p = NULL;//将p变为空指针
free(p);//释放空指针是不会产生变化的
p = NULL;
}
6.动态开辟内存忘记释放(内存泄漏)
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return -1;
}
//使用
//忘记释放了
getchar();//当程序不释放也不结束的时候,程序一直在运行,如果一个程序一直在开辟空间却不释放也没有结束程序,就会容易造成内存泄漏
return 0;
}



