C语言进阶第九课 --------动态内存管理-1
https://developer.aliyun.com/article/1498057
经典笔试题
第一题
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
上面这些代码主要的错误就是当我们调用Test函数时,也会调用到 GetMemory函数,而传参传递的时变量str的值,而不是地址,最后str的值还是NULL,还造成了内存泄漏,对NULL解引用就会报错,如果把 GetMemory函数更改为 GetMemory(char* *p ),然后传递str的地址
更改后:
void GetMemory(char* *p) { *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str); str = NULL; } int main() { Test(); return 0; }
还要注意的printf函数的使用没有错,是可以这样写的,这个写法只针对字符串
第二题
#include <stdio.h> #include <stdlib.h> #include <limits.h> char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
这里的错误是GetMemory函数在调用结束后,p的空间销毁掉了,返回的值也就成了野指针
第三题
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include<string.h> void GetMemory(char** p, int num) { *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } int main() { Test(); return 0; }
这道题主要是少了free释放,内存泄漏了
第四题
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include<string.h> void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; }
这里的错误就是野指针,在我们释放了动态开辟的内存,str还是指向那块地址,这样就会非法访问内存,这里我们要注意释放后一定要赋值NULL
C/C++中程序内存区域分布
其中代码段是用来存储代码翻译成的二进制指令和只读常量(字符串常量),里面的数据不可更改
这个图就可以很明了的知道动态内存开辟的空间是在堆区,局部变量和形式参数的空间是在栈区m全局变量和静态变量的空间是在数据段(静态区)
还要注意的是函数的空间也是在栈上创建的,函数栈帧的创建和销毁前面我已经写过,可以去看看
C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
柔性数组
这个概念是在C99引入的,之前是没有的
形成柔性数组的条件:
- 要在结构体中
- 是结构体的最后一个成员,并且这个数组是一个未知大小的数组(变长数组)
写法:
struct S { int a; int arr[];//柔性数组 }a1; struct S1 { int a; int arr[0];//柔性数组 }a2;
代码中 的两个结构体属于两种写法,
柔性数组的特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include<string.h> struct S1 { int a; int arr[0]; }; int main() { printf("%d", sizeof(struct S1); return 0; }
可以看到sizeof返回的大小不包含柔性数组,假设结构体只有一个成员,而这个成员是柔性数组就会发现这个结构体的大小是0
3. 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include<string.h> struct S1 { int a; int arr[0]; }; int main() { struct S1* p = (struct S1*)malloc(sizeof(int) + 20); if (p == NULL) { perror("malloc"); return; } p->a = 10; int i = 0; for (i = 0; i < 4; i++) { p->arr[i] = i; } free(p); p = NULL; return 0; }
其实我们可以不使用柔性数组,我们也可以模拟出来
#include <stdio.h> #include <stdlib.h> #include <limits.h> #include<string.h> struct S1 { int a; int arr[0]; }; struct S2 { int a; int* arr; }; int main() { /*struct S1* p = (struct S1*)malloc(sizeof(int) + 20); if (p == NULL) { perror("malloc"); return; } p->a = 10; int i = 0; for (i = 0; i < 4; i++) { p->arr[i] = i; } free(p); p = NULL;*/ struct S2 a2; a2.a = 10; a2.arr = (int*)malloc(sizeof(int) * 4); if (a2.arr == NULL) { perror("malloc"); return; } printf("%d", sizeof(struct S2)); free(a2.arr); a2.arr = NULL; return 0; }
使用这种方法虽然可以代替柔性数组,但是使用free的次数会增多,而使用柔性数组只需使用一次free
当我们使用malloc开辟的数次越多,产生的内存碎片就会越多,内存的利用率就会下降
使用柔性数组的好处
第一个好处是:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
第二个好处是:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,我个人觉得也没多高了,反正你跑不了要用做偏移量的加法来寻址)
总结
在这里介绍了动态内存的开辟、常见错误柔性数组,有不懂的小可爱可以私聊