前言:不管是c还是c++都会大量使用,使用c/c++的数据结构的时候也会使用的动态内存
目前我们申请内存就只有两种方式,一种是创建变量,一种是创建数组
//创建变量
int a=0;
char c='w';
//创建数组
int arr1[100];
char arr2[10];上面的两种申请空间方式一单申请号就无法改变,所以就引入了今天我们学习的四个函数
void* malloc (size_t size);举个例子:这里我们申请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*是通用指针,可以返回任何类型的指针

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

C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的
void free (void* ptr);#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置为空后就不再是申请空间的起始地址了
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函数就可以做到对动态开辟内存大小的调整
void* realloc (void* ptr, size_t size);情况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
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);
}void test()
{
int a = 10;
int *p = &a;
free(p); //对非动态内存的空间进行释放,创建的变量都是在栈区
}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也会产生死循环
忘记释放不再使用的动态开辟的空间会造成内存泄漏, 切记:动态开辟的空间⼀定要释放,并且正确释放