![](https://img-blog.csdnimg.cn/img_convert/d13d2c275463463daae6da27442d62c0.png)
动态内存分配的意义
已掌握的开辟方式
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
以上两种开辟方式具有两个特点
1.空间开辟大小是固定的。
⒉数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能尝试动态内存的开辟了。
动态内存分配函数
头文件---stdlib.h
malloc
void * malloc ( size_t size );
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查(空指针判断)。
返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数size为0,malloc的行为是标准是未定义的,取决于编译器。
free
void free ( void * ptr );
free专门用来做动态内存的释放和回收。
如果参数ptr指向的空间不是动态开辟的(堆上的非栈上的),那free函数的行为是未定义的。
如果参数ptr是NULL指针,则函数什么事都不做。
![](https://img-blog.csdnimg.cn/img_convert/8b3fab935e5d464e905794b6448be548.png)
calloc
void * calloc ( size_t num, size_t size );
Allocates an array in memory with elements initialized to 0.
函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数ma1loc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
![](https://img-blog.csdnimg.cn/img_convert/270d37007c37418bb02e954ec7c8788b.png)
realloc
void * realloc ( void* ptr, size_t size );
ptr是要调整的内存地址
size调整之后新大小
![](https://img-blog.csdnimg.cn/img_convert/c3a0c43f18244a0898cf9e2f0ef5bcdf.png)
注:由于realloc申请内存失败可能会返回空指针,若使用原有的指针直接接收可能会导致原有指针指向的目标空间丢失,故使用新的指针来接收,从而来判断是否为空指针。
//判断空指针
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr)
p = ptr;
realloc也可以实现类似malloc的功能
int* p = (int*)realloc(NULL, 40);
常见的动态内存错误
动态内存造成的错误一般会导致程序直接卡死
![](https://img-blog.csdnimg.cn/img_convert/fc521f36c2d946589a9610671e2c0f86.png)
对非动态内存使用free释放
![](https://img-blog.csdnimg.cn/img_convert/28f1bf80bef84ff6812385693d172311.png)
使用free释放一块动态开辟内存的一部分
![](https://img-blog.csdnimg.cn/img_convert/cf8bb4adb5c24011a2ee95738880d161.png)
对同一块动态内存多次释放
![](https://img-blog.csdnimg.cn/img_convert/7b1b614b01614e9aa62782af8fd5a4ab.png)
动态开辟内存忘记释放(泄露)
所开辟的动态内存无释放,且无指针指向这块内存。
![](https://img-blog.csdnimg.cn/img_convert/56eceb7c85144a7c9d34dc648990c35c.png)
形参销毁导致无指针指向开辟的空间,引起内存泄漏。
![](https://img-blog.csdnimg.cn/img_convert/5a9845f84f844e2b8c0d9ac4dfed67d2.png)
![](https://img-blog.csdnimg.cn/img_convert/759c9394e31146c08aee6995d6332caa.png)
经典笔试题
题目一
函数结束后局部变量自动销毁,返回的指针指向无意义。
![](https://img-blog.csdnimg.cn/img_convert/21b3d1990de449c5860663b969856473.png)
题目二
*ptr使用了野指针。
![](https://img-blog.csdnimg.cn/img_convert/8e2937fb9f80498bad248c94e2b8f3d6.png)
题目三
内存泄漏。
![](https://img-blog.csdnimg.cn/img_convert/af1aecf0de3a4a0c8411845e03f6e07b.png)
题目四
无明显问题,采用了二级指针,通过*p找到了str。
![](https://img-blog.csdnimg.cn/img_convert/0c1c93a5f7a54023b0594e644054873e.png)
只需在函数末尾进行释放和给指针赋空值即可。
![](https://img-blog.csdnimg.cn/img_convert/102569ca17d448fc83e99793708035c7.png)
题目五
free之后指针还是指向开辟的空间,而后非法访问。
![](https://img-blog.csdnimg.cn/img_convert/bf22e4dd63094741a326fcf14689e079.png)
C/C++程序的内存开辟
![](https://img-blog.csdnimg.cn/img_convert/54de3f1b88884f959260623ea17850f9.png)
代码经过编译->可执行代码->储存在代码段中
常量字符串->只读常量->储存在代码段中
C/C++程序内存分配的几个区域:
1.栈区(stack):在执栖函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。内存分配运算内置于处理器的指令集中,
效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2堆区 (heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
3.数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。
4.代码段:存放函数体(类成员函数和全局函数)的二进制代码。
柔性数组
柔性数组的特点
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
//或者是int a[];
//取决于不同的编译器
}type_a;
printf("%zd\n", sizeof(type_a));//输出结果为4
柔性数组的使用
例如:
struct S
{
int i;
int a[0];
};
void main()
{
//给a分配10整型的内存大小
struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
if (ps == NULL)
{
perror("malloc");
return;
}
ps->i = 10;
for (int i = 0; i < 10; i++)
ps->a[i] = i;
//给a扩容
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + sizeof(int) * 20);
if (ptr == NULL)
{
perror("realloc");
return;
}
ps = ptr;
for (int i = 10; i < 20; i++)
ps->a[i] = 1;
for (int i = 0; i < 20; i++)
printf("%d ", ps->a[i]);
//释放
free(ps);
ps = NULL;
}
柔性数组的优势
第一个好处:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,升把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体 的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及基成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处:有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)
柔性数组动态开辟
柔性数组的空间与结构体其他成员变量连在一起,可以一块释放。
//柔性数组
struct S
{
int i;
//或者是int a[];
//取决于不同的编译器
int a[0];
};
void main()
{
struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int)*10);
if (p == NULL)
{
perror("main");
return;
}
p->i = 10;
for (int i = 0; i < 10; ++i)
p->a[i] = i;
for (int i = 0; i < 10; ++i)
printf("%d ", p->a[i]);
//含有柔性数组的结构体开辟的空间是连接在一块的
//可以直接释放结构体指针
free(p);
}
非柔性数组的动态开辟
非柔性数组的动态开辟产生的空间不在一块,是分散的,因此要逐个释放。
//非柔性数组动态开辟
struct S
{
int i;
int* a;
};
void main()
{
//malloc1
struct S* ps = malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc1");
return;
}
ps->i = 10;
//malloc2
ps->a = (int*)malloc(sizeof(int) * 10);
if (ps->a == NULL)
{
perror("malloc2");
return;
}
ps->i = 10;
for (int i = 0; i < 10; ++i)
ps->a[i] = i;
for (int i = 0; i < 10; ++i)
printf("%d ", ps->a[i]);
//realloc
int* ptr = (int*)realloc(ps->a, sizeof(int) * 20);
if (ptr != NULL)
ps = ptr;
for (int i = 0; i < 20; ++i)
ps->a[i] = i;
//非柔性数组动态开辟的释放需要逐步进行
free(ps->a);
ps->a = NULL;
free(ps);
ps = NULL;
}
详细资料
C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell
扩展
使用malloc会导致内存碎片增多,内存碎片越多会导致内存利用率降低。
![](https://img-blog.csdnimg.cn/img_convert/9a705fa2921e4cae9135ad092ff26047.png)
在软件设计中,采用内存池来规划好软件取用的内存区域,提升内存利用率。
![](https://img-blog.csdnimg.cn/img_convert/b404b5bb6a904075b89759e2a513a82f.png)
局部性原理中的空间局部性,程序大部分取用相近的内存。
![](https://img-blog.csdnimg.cn/img_convert/bd335bda4283488bb22472ae681c039c.png)