动态内存管理
前言
在前面的静态通讯录这篇博客中,发现我们所写的数组大小一旦确定好,就会向内存空间申请一块固定的连续空间,当我们使用的时候可能会出现内存浪费和内存不够用的情况。
而今天介绍的动态内存管理可以在一定程度上解决这个问题。
动态内存
内存区一般最常见的是这三个
栈区(Stack):编译系统自动分配释放,主要存放 函数参数,局部变量 等 .
堆区(heap):由程序员分配释放管理,一般由 malloc,new等内部存储函数使用, 如果没收回,程序结束时由操作系统收回。创建堆时,一般在堆的头部 用一个字节存放堆的大小;回收堆时,通过查看这个 字节的内容,可得知需要释放的多大的内存。
静态区:存放 全局变量 和 静态变量 ,程序结束时由系统释放,分为全局初始化区和全局未初始化区。
常量区:存放 常量 ,程序结束时由系统释放 。
程序代码区(上面4个区统称数据区):存放运行 或准备运行的程序代码,由系统调度
动态内存函数
malloc函数
void* malloc (size_t size);
该函数向堆区申请了一块连续的空间,同时返回这块空间的指针
注意事项:
如果开辟成功,则返回一个指向开辟好空间的void类型指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* tmp = (int*)malloc(40); //用malloc申请40个字节,用来存放10个整形
if (tmp == NULL) //判断malloc是否申请成功
{
printf("%s\n", strerror(errno));
return 0;
}
for (int i = 0; i < 10; i++) //malloc申请的是一块连续的空间,可以当作数组来使用
{
tmp[i] = i + 1; //存放1~10
printf("%d ", tmp[i]); //存放之后直接打印
}
return 0;
}
free函数
void free(void* ptr);
注意事项:
1.如果 ptr指针所指向的空间不是动态内存开辟的,那么free函数的作用是未定义的,很可能会导致程序崩溃
2.如果 ptr是NULL,那么free函数将什么都不会做
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* tmp = (int*)malloc(40); //用malloc申请40个字节,用来存放10个整形
if (tmp == NULL) //判断malloc是否申请成功
{
printf("%s\n", strerror(errno));
return 0;
}
for (int i = 0; i < 10; i++) //malloc申请的是一块连续的空间,可以当作数组来使用
{
tmp[i] = i + 1; //存放1~10
printf("%d ", tmp[i]); //存放之后直接打印
}
free(tmp);
tmp=NULL;
return 0;
}
运行结果:
calloc函数
void* calloc(size_t num,size_t size);
向内存申请一块 num个 size大小的内存空间,并把空间内每个字节初始化为0
注意事项:
如果开辟成功,则返回一个指向开辟好空间的void类型指针。
如果开辟失败,则返回一个NULL指针,因此calloc的返回值一定要做检查。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* tmp = (int*)calloc(10,sizeof(int)); //向内存申请10个 int类型大小的空间
if (tmp == NULL) //判断是否判断成功
{
printf("%s\n", strerror(errno));
return 0;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", tmp[i]); //打印
}
free(tmp);
tmp = NULL;
return 0;
}
运行结果:
realloc函数
void* realloc(void* ptr,size_t size);
realloc 函数的出现,使得动态内存管理更加的灵活。例如有些时侯我们觉得前面申请的空间太小了不够用,或者我们会觉得申请的空间过大了太浪费,这个时候我们就可以通过使用 realloc 函数对之前开辟的动态内存空间的大小再次进行合理的调整。
- ptr指针是指向要调整的内存起始地址
- size:调整之后的总字节大小
- 返回值是指向 调整之后空间的起始地址
- 调整失败,返回空指针,所以这里也需要判断
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* tmp = (int*)calloc(10,sizeof(int));
if (tmp == NULL)
{
printf("%s\n", strerror(errno));
return 0;
}
int* p = (int*)realloc(tmp, 20 * sizeof(int)); //调整空间大小
if (p == NULL) //判断调整是否成功
{
printf("%s\n", strerror(errno));
return 0;
}
tmp = p;
for (int i = 0; i < 20; i++)
{
printf("%d ", tmp[i]);
}
free(tmp);
tmp = NULL;
return 0;
}
运行结果:
如果扩容成功,也分两种扩容情况:当前空间与后相邻空间之间的空间是否足够 realloc 函数进行扩容操作。
扩容情况一:
若空间足够,则直接执行扩容操作,并在扩容完成后返回指向起始位置的指针。
扩容情况二:
若后续空间不够,则将会在堆区中重新寻找合适的空间(足以容纳下扩容后的全部空间),并将原空间内的数据全部拷贝过来,接着释放原空间,并在扩容完成后返回指向新空间起始位置的指针。
常见的动态内存错误
1.判断是否成功开辟
列:
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + 1) = 0;
}
free(p);
p=NULL;
return 0;
}
上面代码中,在 malloc 执行后,没有对 p 指针进行检查,因为 malloc 也可能失败,失败时会返回一个空指针,如果是这样,那下面就是对空指针进行解引用操作,这样是不合适的。
2.对动态内存的越界访问
int main()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return 1;
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问,会导致程序挂掉
}
free(p);
p = NULL;
}
3.对非动态内存的空间使用free
int main()
{
int a = 10;//栈区
int* p = &a;
free(p);
return 0;
}
需要注意free针对的是堆区上的空间,而上述代码中的 p 指针指向一个整型变量,整型变量是在栈区申请的空间
4.使用 free 函数释放动态内存空间的一部分(free释放时要从头释放):
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+1;
p++;
}
free(p);
p=NULL;
}
上面代码中的 p 指针,在经历过多次 p++ 之后就不再指向这个整型的起始地址了,此时再去 free 就会出问题。
避免此类错误的方法是,在使用指针前保存好初始指向,并在进行动态内存释放时释放完整的动态内存空间。
5.对同一块动态内存空间多次释放(一定要记得在释放空间后把指针置空):
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
free(p);
//...(在中间又进行了很多其它操作之后,忘记了已经释放过动态内存空间,并进行了重复释放)
free(p);
//重复释放动态内存空间
p = NULL;
return 0;
}
如上面代码,在程序的最后对 p 指针 free 了两次,此时程序就会报错。本质原因就是对同一块动态内存多次释放程序会报错。但是如果在第一个 free 的后面把 p 指针赋为空指针,就算再 free就没有任何问题了。
6.不释放动态内存空间(会导致内存泄漏):
void test()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p != NULL)
{
*p = 10;
//判断非空后进行使用
}
//使用后没有释放动态内存空间,在程序终止前该动态内存空间都不会被释放,将会占用计算机系统的内存
}
int main()
{
test();
while (1);
//为了演示内存泄漏,使程序不终止
return 0;
}
避免此类问题的方法是,是使用一个释放一个