目录
1.为什么要有动态内存分配
2.malloc函数和free函数
malloc
函数原型
栗子
free
函数原型
栗子
3.calloc和***realloc***
3.1calloc函数
原型如下:
栗子
3.2***recalloc***
第一种情况
第二种情况
第三种情况
recalloc模拟实现calloc函数
4.六大常⻅的动态内存的错误
4.1对NULL指针的解引⽤操作
4.2对动态开辟空间的越界访问
4.3对⾮动态开辟内存使⽤free释放
4.4使⽤free释放⼀块动态开辟内存的⼀部分
4.5对同⼀块动态内存多次释放
4.6 动态开辟内存忘记释放(内存泄漏)
5.动态内存经典笔试题分析
5.1题目一
正确解法
5.2题目二
正确解法
5.3题目三
正确解法
5.4题目四
正确解法
6. 柔性数组
6.1什么是柔性数组
栗子
6.2柔性数组的使用
6.3柔性数组的优势
1.为什么要有动态内存分配
int val = 20;// 在栈空间上开辟四个字节 char arr[10] = {0};// 在栈空间上开辟 10 个字节的连续空间
但是上述的开辟空间的⽅式有两个特点:
• • 空间开辟⼤⼩是固定的。
* *数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知 道,那数组的编译时开辟空间的⽅式就不能满⾜了。 C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。
2.malloc函数和free函数
malloc
是C语言用于动态内存开辟的函数
函数原型
void* malloc (size_t size);
说明:malloc向系统申请分配指定size个字节的内存空间。返回类型是void *类型。void *类型表示未确定类型的指针。C、C++规定,void *类型可以强制转换为任何其他类型的指针。
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
******#include//malloc头文件
• • • • 如果开辟成功,则返回⼀个指向开辟好空间的指针。
*******如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查。
*******void* 所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候⾃⼰来绝定。
********如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器
栗子
因为malloc()返回值是返回函数开辟空间的地址,类型是void,所以p应该是要用void * p来接收,但是void类型的指针,是不能进行解引用的,所以这个方法行不通。
int* p = (int *)malloc();
要把malloc返回值进行强制转换成你要开辟空间用于什么数据类型的类型。
比如这里我需要int类型的内存,所以这里要转换成int类型。
重要一步检查是否内存开辟成功也莫忘:
如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针
int* p = (int *)malloc(40); if (p == NULL) { perror("malloc");//报错 return 1; } //使用开好的内存 for (int i = 0; ifree
函数原型
void free (void* ptr);free参数是动态内存申请好返回的指针变量。
函数free,专⻔是⽤来做动态内存的释放和回收的
**free函数必须和malloc函数同时使用不然会报错
**free函数无法释放栈内存的变量
**free只能释放动态内存申请的空间
栗子
int* p = (int *)malloc(40); if (p == NULL) { perror("malloc");//报错 return 1; } //使用开好的内存 for (int i = 0; i3.calloc和***realloc***
3.1calloc函数
原型如下:
void* calloc (size_t num, size_t size);
****函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
***malloc与calloc区别就是malloc不会初始化,而calloc初始化为0
***第二就是,参数不一样,calloc可以输入指定个数,和大小
栗子
//使用calloc函数进行动态内存申请 int* p = (int*)calloc(10, sizeof(int));3.2***recalloc***
realloc函数可以做到对动态开辟内存⼤小调整;
realloc函数的出现让动态内存管理更加灵活。
函数原型
void* realloc (void* ptr, size_t size);realloc分为三种情况,
第一种情况
申请空间失败;
第二种情况
申请成功,但开辟空间的时候后面没有足够的空间,在这种情况下,realloc函数会在内存的堆区重新找一个空间(这个空间满足新的申请空间大小需求),同时会把旧的数据拷贝到新的空间,然后释放旧的空间,同时返回新的空间的起始地址。
第三种情况
也是成功情况,但是这次后面的空间是足够的情况,可以直接进行扩大,扩大空间后,直接返回旧的空间的起始地址。
//要用一个临时变量存储申请的空间,防止申请失败而置为null int* p = (int*)calloc(10, sizeof(int)); int ptr = (int*)reallco(p, 10 * sizeof(int)); if (ptr != NULL) { p = ptr; } else { perror("realloc"); return 1; }recalloc模拟实现calloc函数
int* p = (int *)realloc(NULL,40); if (p == NULL) { perror("malloc");//报错 return 1; } //使用开好的内存 for (int i = 0; i4.六大常⻅的动态内存的错误
4.1对NULL指针的解引⽤操作
没有进行判断是否申请内存成功而直接使用
int a = 20; int* p = (int*)malloc(40); *p = a;4.2对动态开辟空间的越界访问
int* p = (int*)malloc(40); //类似于数组的越界访问 if (NULL == p) { exit(EXIT_FAILURE); } for (int i = 0; i i = 10; for (int i = 0; i a[i] = i; } struct st *ptr = (struct st*)realloc(ps, sizeof(struct st) + 15 * sizeof(int)); if (ps = NULL) { perror("ps"); return 0; } else { ps = ptr; }第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤ ⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能 指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返 回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度. 连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你 跑不了要⽤做偏移量的加法来寻址