动态内存管理
1. 需要动态内存分配的原因
在c/c++语言中,编写程序有时不能确定数组应该定义为多大,因此这时在程序运行时要根据需要从系统中动态多地获得内存空间。所谓动态内存分配,就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
申明数组时,需要给定数组的大小,而 数组大小一旦确定就不能调整。所以需要动态内存分配供我们灵活使用。
2. malloc 和 free
2.1 malloc
malloc 结构:void* malloc (size_t size);
malloc: 用于申请一块连续的指定大小的内存块区域以 void* 类型返回分配的内存区域地址,当无法知道内存具体位置的时候,想要绑定真正的内存空间,就需要用到动态的分配内存,且分配的大小就是程序要求的大小。(使用 malloc 需要头文件 #include<stdlib.h>)
返回值: 1. 使用 malloc 申请成功后,指向函数分配的内存块的指针。
2. 若申请失败,则返回 null 指针。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
return 0;
}
这样又引出了一个问题,malloc申请的空间又如何回收呢?free 就可以来解决这个问题了。
2.2 free
free 结构:void free (void* ptr);
free: 释放内存空间的函数*(free只能释放动态开辟的内存)*,通常与申请内存空间的函数malloc()结合使用,可以释放由 malloc( )、calloc( )、realloc( ) 等函数申请的内存空间。(使用 free 需要头文件 #include<stdlib.h>)
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* ptr = (int*)malloc(40);
if (ptr != NULL)
{
for (int i = 0; i < 10; i++)
{
*(ptr + i) = i;
}
}
free(ptr); //释放空间
ptr = NULL; //释放空间后ptr成了野指针,需及时置空
return 0;
}
回到刚才的问题,除了使用 free 释放空间,还有一种情况。有时候忘记释放空间也是常有的事,所以即使我们不用 free 释放空间,到了程序结束后,操作系统也会自动回收,但是为了养成一个好习惯,还是需要主动加上 free !
3. calloc 和 realloc
3.1 calloc
calloc 结构:void* calloc (size_t num, size_t size);
calloc: 在内存的动态存储区中分配 num 个长度为 size 的连续空间,并且把空间的每个字节初始化为 0,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。(使用 calloc 需要头文件 #include<stdlib.h>)
例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* pc = (int*)calloc(10, 4);
if (pc != NULL)
{
for (int i = 0; i < 10; i++)
{
printf("%d ", pc[i]);
}
}
free(pc); //释放空间
pc = NULL; //置空
return 0;
}
运行结果:
0 0 0 0 0 0 0 0 0 0
malloc 与 calloc 区别:
malloc 申请空间后会直接返回起始地址;calloc 申请完空间后先进行初始化(初始化为 0),再返回起始地址。
calloc 相较于 malloc 多了一步初始化,两者可根据需求灵活使用。
3.2 realloc
realloc 结构:void* realloc (void* ptr, size_t size);
realloc: 更改所指向的内存块的大小。(ptr 为要调整的内存地址;size 为调整之后新大小;)(使用 realloc 需要头文件 #include<stdlib.h>)
size: 新的大小可大可小(如果新的大小 大于 原内存大小,则新分配部分不会被初始化;如果新的大小 小于 原内存大小,可能会导致数据丢失)
注意: 这里的 size 是变化后的大小,而不是原有的大小 加上 size!!!
例:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* pm = (int*)malloc(10);
if(pm!=NULL)
{
pm = (int*)realloc(pm, 40); //扩展空间大小
for (int i = 0; i < 10; i++)
{
*(pm + i) = i;
}
}
free(pm);
pm = NULL;
return 0;
}
3.2.1 realloc 调整内存空间的两种情况
- 原空间后有足够的空间。
- 原空间后没有足够的空间。
接下来就用图片进行说明:
其中情况 1 较好理解,后面空间足够可以直接扩展。重点在于情况 2 ,后面空间不够就需要另申请一块新的空间,将原有的数据拷贝到新空间后,原空间就会释放。
以上函数都在堆区上进行操作
提醒:
- 不要对NULL指针进行解引用操作。
- 记清开辟的空间大小,以免出现越界访问的情况。
- 不要对非动态开辟的内存进行 free 释放操作。
- free 是对指向动态内存起始位置 的数据进行释放操作,记得留意动态内存的指向位置。
- 不要对一块动态内存多次释放。
- 如果要开辟动态内存,一定记得要进行 free 操作,避免内存泄漏的情况发生。
4. 柔性数组
例:
struct S
{
char a;
int b;
int c[0]; //柔性数组成员
//或int c[];
};
4.1 柔性数组的特点
- 结构中的柔性数组成员前面必须至少⼀个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用 malloc ( ) 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
struct S
{
char a;
int b;
int c[0]; //柔性数组成员
//或int c[];
};
int main()
{
struct S s;
printf("结构体大小:%d",sizeof(s)); //8个字节
struct S* ps = (struct S*)malloc(sizeof(struct S) + 40); //共48个字节
if (ps == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
ps->c[i] = i;
}
free(ps);
ps = NULL;
//还可使用realloc继续改变内存空间
//···
return 0;
}
运行结果:
结构体大小:8 //不计柔性成员的大小
柔性数组的使用不多,可根据实际情况灵活使用。