深度解析动态分配内存管理
目录
编辑
一. 前言
二.正文
2.0 计算机中的内存
2.1 动态申请函数头文件
2.2 malloc函数
2.3 free函数
2.3 calloc函数
2.4 realloc函数
2.5 经典笔试题
1.
2.
2.6 柔性数组
三.结语
一. 前言
本小节跟大家分享动态内存管理的知识,希望能给大家带来些收获。
二.正文
2.0 计算机中的内存
我们知道目前内存有,栈区,堆区,静态区。
C/C++ 程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些 存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)回收 。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
目前我们有这些开辟内存空间的方法:
int h = 100; // 在静态区创建全局变量int main(){static z = 10; // 变量储存在静态区int val = 20 ; // 在栈空间上开辟四个字节char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间return 0;}
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。2. 数组在申明的时候, 必须 指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编 译时开辟空间的方式就不能满足了。 这时候就只能试试动态存开辟了。
2.1 动态申请函数——头文件
#include<stdlib.h>
2.2 malloc函数
void* malloc ( size_t size ); // szie 字节数的意思,可以直接填数字
C 语言提供了一个动态内存开辟的函数:
这个函数向内存堆区申请一块 连续可用 的随机空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好空间的指针。
- malloc可以申请0字节的空间,会返回一个没有空间的指针,不能访问。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
- 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
运用:
int * a = (int *)malloc(4); // 4可以改成 sizeof(int)
if (a == NULL)
{perror("malloc"); // 开辟失败打印原因
}
2.3 free函数
void free ( void* ptr );
free 函数用来释放动态开辟的内存。
- 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数 ptr 是NULL指针,则函数什么事都不做。
- 当我们不释放动态申请的内存时,如果持续申请内存,那么电脑内存不断变少,没有内存时就会死鸡,当程序结束时,操作系统自动回收内存。
- 如果程序不结束,那么不回收的内存,会越来越多,这就是出现内存泄露问题。
2.3 calloc函数
void* calloc ( size_t num , size_t size );
参数解析:
- num: 创建数据类型的个数。
- size: 每个数据类型所占的字节数。
特点:
- 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include <stdio.h>
#include <stdlib.h>
int main()
{int* a = (int*)malloc(8); // 2个整型int* b = (int*)calloc(2,sizeof(int)); // sizeof(int) 可以是 4,反正表示字节数return 0;
}
查看内存验证:
2.4 realloc函数——调整空间函数
void* realloc ( void* ptr , size_t size );
参数解析:
- ptr :是要调整的内存地址。(如果为空指针,那么同malloc功能类似。)
- size :调整之后新大小
- 返回值 : 为调整之后的内存起始位置。
realloc 函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存, 我们一定会对内存的大小做灵活的调整。
realloc 函数就可以做到对动态开辟内存大小的调整。 函数原型
如下:
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc 在调整内存空间的是存在两种情况:
- 情况1:原有空间之后有足够大的空间

- 情况2:原有空间之后没有足够大的空间
看以下代码,思考代码那里不合理:
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;p = (int*)realloc(p, sizeof(int));printf("%d", *p);return 0;
}
我们可以看出,这里没有判断realloc是否成功,如果申请失败,返回NULL,那么p就会丢掉原有的地址,这是不合理的,因此我们需要进行判断,所以正确的代码是:
#include <stdio.h>
#include <stdlib.h>
int main()
{int a = 10;int* p = &a;int *tmp= (int*)realloc(p, sizeof(int));if (tmp == NULL){perror("realloc");return -1;// 或者exit(-1);}p = tmp;printf("%d", *p);return 0;
}
2.5 经典笔试题
1.
思考:程序结果
char* GetMemory(void)
{char p[] = "hello world"; // 栈区开辟,函数结束内存收回return p;
}
void Test(void)
{char* str = NULL;str = GetMemory(); // 非法访问。访问被系统收回的内存printf(str); // 无法打印,已被覆盖
}int main()
{Test();return 0;
}
2.
程序中存在的问题
void Test(void)
{char* str = (char*)malloc(100); // 缺少对malloc返回值检查/*加上:if(str == null){perror("malloc");exit(-1);}*/strcpy(str, "hello");free(str); // 加上 str = null;if (str != NULL) // str还存有原先内存的地址,出现野指针 {strcpy(str, "world");printf(str);}
}int main()
{Test();return 0;
}
2.6 柔性数组
也许你从来没有听说过 柔性数组( flflexible array ) 这个概念,但是它确实是存在的。 C99 中,结构中的最 后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
例如:
typedef struct st_type{int i ;int a []; // 柔性数组成员} type_a ;
特点:
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
例如:
typedef struct mystruct
{double a;int c[];
}MS;int main()
{printf("%d\\n", sizeof(MS)); // 8return 0;
}
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:
代码1
typedef struct mystruct
{double a;int c[];
}MS;int main()
{MS * p1 = (MS* )malloc(sizeof(MS) + 40); // 那就是48个字节,40就是对柔性数组申请的 //预期空间return 0;
}
也可以这样:
代码2
typedef struct mystruct
{double a;int *c;
}MS;
int main()
{MS * p1 = (MS* )malloc(sizeof(MS)); if (p1 == NULL)
{perror ( "malloc");exit(-1);
}p1->c = (int *)realloc(c, 40);return 0;
}
代码1相较于代码2的优势:
- 方便释放内存。代码2需要2次释放内存。
- 有利于提高数据命中率,提升运行效率,减少内存碎片。
三.结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。