1、为何要有动态内存管理
我们已经掌握的内存开辟方式有
int val = 10;//在栈上开辟4个字节
char arr[10] = { 0 };//在栈上开辟10个字节
以上开辟空间的方式有以下特点
- 空间开辟的大小是固定的
- 数组在声明的时候,必须指定数组的长度,数组空间一旦确定了,大小不能调整
仅仅上述方式并不能不能满足我们对于空间的需求,有时候我们需要的空间大小在运行程序的时候才能知道。所以C语言引入了动态内存开辟,我们得以自己申请和释放空间。
2、malloc和free
2.1malloc
malloc的声明如下
void* malloc (size_t size);
这个函数的功能是在堆区中开辟一块大小为size个字节的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回指向这块空间的指针。
- 如果开辟失败,则返回一个NULL的指针,所以我们在使用前要对malloc的返回值进行检查
- 由于返回值的类型是void*,所以我们在具体使用时要将其强制转换为我们需要的类型
- 如果size为0,malloc的行为标准是未定义的,取决于编译器
2.2free
free的声明如下
void free (void* ptr);
free是用来释放堆区的内存(也就是动态开辟出的内存)
- 如果参数ptr指向的空间不在堆区,那么free函数是未定义的
- 如果ptr指向NULL,那么函数什么事情都不做
举个例子
#include<stdio.h>
#include<stdlib.h>
//malloc,free等函数的声明在stdlib.h中
int main()
{
int num = 0;
scanf("%d", &num);
int* arr = (int*)malloc((num * sizeof(int)));//在堆区中开辟num个int大小的空间
if (arr == NULL)//判断是否开辟成功
{
perror(malloc);//报错
return 1;//提前返回
}
//使用
free(arr);//此时释放arr指向的空间
arr = NULL;//将arr置为空,否则arr将变成野指针
return 0;
}
调试查看,输入num=10,查看arr指向的内存
可以看到恰好开辟出了40个字节的空间,但并未进行初始化,
继续调试到释放后
到此程序结束,这便是malloc与free的使用方法
3.calloc与realloc
3.1calloc
声明:
void* calloc (size_t num, size_t size);
- 该函数时为num个大小为size的元素开辟一片空间,并吧空间的每个字节初始化为0(这点与malloc不同)。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int* arr = (int*)calloc(num, sizeof(int));//开辟空间
if (arr == NULL)//判断
{
perror(calloc);
return 1;
}
//使用
free(arr);//释放
arr = NULL;
return 0;
}
调试以上代码同样输入num=10;观察arr指向的内存
可以看到同样是40个字节,使用calloc开辟的空间被初始化为全0,而malloc则是未初始化的随机值
所以如果我们对申请的内存空间要求初始化,那么使用calloc更为方便
3.2 relloc
relloc的出现让动态内存管理更加灵活
有时我们申请到的内存与实际使用需要的的内存相比会存在偏大偏小的内存,为了合理使用内存,我们会对内存的大小进行灵活的调制,那么就需要用到relloc了
声明:
void* realloc (void* ptr, size_t size);
- ptr是要调整的内存的地址
- size为调整之后的大小
- 返回值是调整之后内存的起始位置
- realloc在调整内存空间时会存在两种情况:1.原有空间之后有足够大的空间 ;2.原有空间之后没有足够大的空间
情况1:
此时ptr指向空间之后的空间足够,realloc就直接向后追加空间
情况2:
此时ptr指向空间之后的空间不够,realloc会在堆区找一块合适大小的连续空间来开辟内存,并将ptr原来指向空间的数据拷贝过来,然后释放掉ptr原来指向的空间,最后返回新开辟出的内存的地址,若内存中没有这样大小的连续空间,则返回一个空指针
考虑上述两种情况,realloc的返回值不能直接用ptr来接收,需要创建一个中间指针来接收,并判断是否为空指针后赋值给ptr
如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)malloc(100);
if (ptr == NULL)
{
perror(malloc);
return 1;
}
//使用内存
//扩展容量
int* tmp = NULL;//创建中间指针
tmp = (int*)realloc(ptr, 1000);
if (tmp != NULL)
{
ptr = tmp;
}
//使用内存
free(ptr);
ptr = NULL;
return 0;
}
4、柔性数组
所谓柔性数组就是元素个数可以发生改变的数组,那么柔性数组就必然离不开动态内存管理;在C99标准中,结构体的最后一个元素允许时位置大小的数组(注意:必须是结构体最后一个元素),这个元素就是柔性数组的成员。
例如
struct S
{
int i;
int a[];//柔性数组成员
};
4.1柔性数组的特点
- 结构体中的柔性数组成员前必须要有一个其他元素
- sizeof计算出这种结构提大小不包括柔性数组成员的大小
- 包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤ ⼩,以适应柔性数组的预期⼤⼩。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
printf("%d\n", sizeof(type_a));//输出结果为4
return 0;
}
4.2柔性数组的使用
代码1
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
if (p == NULL)
{
perror(malloc);
return 1;
}
p->i = 100;
for (int j = 0; j < 100; j++)
{
p->a[j] = j;
}
free(p);
p = NULL;
return 0;
}
这样柔性数组成员a便相当于获得了可以存放100个整形元素的连续空间,也就相当于a[100];
当然,也可以继续调整数组a的大小
如下:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
type_a* p = (type_a*)malloc(sizeof(type_a) + 100 * sizeof(int));
if (p == NULL)
{
perror(malloc);
return 1;
}
p->i = 100;
for (int j = 0; j < 100; j++)
{
p->a[j] = j;
}
//重新调整数组大小
type_a* ptr = (type_a*)realloc(p,sizeof(type_a) + 200 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
//使用.....
free(p);
p = NULL;
return 0;
}
我们也可以用指针来实现柔性数组
代码2
typedef struct st_type
{
int i;
int* pa;
}type_a;
int main()
{
type_a*p = (type_a*)malloc(sizeof(type_a));
p->i = 100;
p->pa = (int*)malloc(100 * sizeof(int));
for (int j = 0; j < 100; j++)
{
p->pa[j] = j;
}
//释放空间
free(p->pa);//一定要先释放pa处的空间
p->pa = NULL;
free(p);
p = NULL;
return 0;
}
上述代码1和代码2的功能相同,但第一个代码的实现更方便内存的释放。
完。