前言:不管是c还是c++都会大量使用,使用c/c++的数据结构的时候也会使用的动态内存
目前我们申请内存就只有两种方式,一种是创建变量,一种是创建数组
//创建变量
int a=0;
char c='w';
//创建数组
int arr1[100];
char arr2[10];
上面的两种申请空间方式一单申请号就无法改变,所以就引入了今天我们学习的四个函数
malloc
void* malloc (size_t size);
- 这个函数向内存申请⼀块连续可用的空间,并返回指向这块空间的指针
- 如果开辟成功,则返回⼀个指向开辟好空间的指针(返回的是空间的起始地址)
- 如果开辟失败,则返回⼀个返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查
- 返回值的类型是void*(因为我只要编译器开辟好空间,但放什么类型的数据编译器就不知道了),所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己决定
- 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译
举个例子:这里我们申请20个字节来存放5个整型数据
#include<stdio.h>
#include<stdlib.h>
int main()
{
int * p=(int*)malloc(20);
if (p == NULL)
{
perror("malloc");//perror将系统错误码(errno)翻译成人类可读的错误信息
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
return 0;
}
这里申请20个字节来存放整型数据就应该要用整形的指针接收,而把malloc的类型转化为整型指针是因为void*是通用指针,可以返回任何类型的指针

而动态申请的内存空间统一都在内存的堆区中(需要手动释放),我们的内存中有栈区,堆区,静态库

free
C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的
void free (void* ptr);
-
free函数用来释放动态开辟的内存
-
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
-
如果参数 ptr 是NULL指针,则函数什么事都不做
-
malloc和free都声明在 stdlib.h 头文件中
#include<stdio.h>
#include<stdlib.h>
int main()
{
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;
}
free(p); //手动释放空间
return 0;
}
可以看到所有的空间的数据全部清空,但地址依旧没变,说明只是将空间的使用权限还给了操作系统,但p指向的指针还是原来的起始位置,这个时候p就指向的是野指针,而我们还要把野指针转化成空指针


free(p);
p=NULL;
如果我们把"*(p + i) = i + 1;"这行代码换成"*p=i+1; p++",这就会改变p指向的原始位置,将p置为空后就不再是申请空间的起始地址了
calloc
void* calloc (size_t num, size_t size);
函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0
例如:向内存申请五个整型类型的空间
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(5, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
return 0;
}
这个时候每个字节里面的初始值都是0

那我们打印出来看一下
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)calloc(5, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
free(p);
p=NULL;
return 0;
}

realloc
realloc函数的出现让动态内存管理更加灵活
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使 用内存,我们⼀定会对内存的大小做灵活的调整,那realloc函数就可以做到对动态开辟内存大小的调整
void* realloc (void* ptr, size_t size);
- ptr 是要调整的内存地址
- size 调整之后新大小
- 返回值为调整之后的内存起始位置
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
- realloc在调整内存空间的是存在两种情况
情况1:原有空间之后有足够大的空间 。 情况2:原有空间之后没有足够大的空间
例如:将原来的20个字节的空间再加上20个字节,这时候就会出现我们上面的两种情况

情况1:这是剩余的空间够用,直接扩大就行
情况2:这就是剩余的空间不够扩大,这时就会在堆区中重新找一块空间开辟20个字节,然后将旧的数据拷贝一份过来在这块新开辟的空间前面,再释放掉旧的空间返回新的空间的起始位置,而之前旧的空间不需要手动释放,realloc会自动释放,如果最后开辟空间失败就会返回NULL
#include<stdio.h>
#include<stdlib.h>
int main()
{
int * p=(int*)malloc(20);
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
p= realloc(p,40);
return 0;
}
这个时候这个代码就出问题了,我开辟40个字节,这时候就差20个字节,如果开辟失败就会把p置为NULL,那我p之前指向的20个字节也就变成了空,就会出现问题
#include<stdio.h>
#include<stdlib.h>
int main()
{
int * p=(int*)malloc(20);
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
int ptr=(int*)realloc(p,40);
if(ptr !=NULL)
{
ptr=p;
}
else
{
perror("realloc");
}
free(p);
p=NULL;
return 0;
}
这才是正确的写法,将创建好的40个字节的空间先暂时给ptr,然后判断ptr是否为NULL,如果不是NULL就将p赋值给ptr
常见的动态内存的错误
对NULL解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
此时我申请了一块空间,但我没有对p进行判断是否为NULL,这个时候就很危险了,此时编译器就会出现警告,他怀疑p的地址为NULL

在这里我们断言判断p是否为NULL
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
int* p = (int*)malloc(INT_MAX);
assert(p);
*p = 20;
free(p);
p=NULL;
}
越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
对非动态开辟内存使⽤free释放
void test()
{
int a = 10;
int *p = &a;
free(p); //对非动态内存的空间进行释放,创建的变量都是在栈区
}
使用free释放⼀块动态开辟内存的⼀部分
void test()
{
int *p = (int *)malloc(100);
p++; //这个时候p指向的地址就不再是申请空间的起始位置
free(p);
}
对同⼀块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);
}
动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
这个代码有几个错误,第一个就是我创建的地址放在了局部变量里面,局部变量出了test()函数就消失了,想释放都来不及了,while也会产生死循环
忘记释放不再使用的动态开辟的空间会造成内存泄漏, 切记:动态开辟的空间⼀定要释放,并且正确释放