3.5 对同一块动态内存多次释放
例如:
void test() { int *p = (int *)malloc(100); free(p); free(p);//重复释放 }
这个问题比较初级,一般也不会有人犯这样的低级错误。
3.6 动态开辟内存忘记释放(内存泄露)
内存泄露是非常严重的问题,虽然说在一个进程结束之后堆开辟的空间也就是动态开辟的空间会被销毁,但是很多时候比如游戏服务器它是不关机一直运行的,如果发生内存泄露这是非常严重的问题。
尽管你可能非常注意避免内存泄漏,但是也可能会出现这样的失误,比如:
void test() { int* p = (int*)malloc(40); //... int flag = 0; scanf("%d", &flag); if (flag == 5) return;//这里如果return 就会出现内存泄露 free(p); p = NULL; } int main() { test(); return 0; }
这段代码就是如此,尽管你注意最后free掉了p,但是在中间如果满足条件return的话还是会出现内存泄露。
四、关于动态内存管理比较经典的题目
4.1 题目1:
void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); }
运行Test函数会有怎样的结果,会出现打印"hello world"吗?
答案肯定不会,首先我们Test函数开始分析,首先GetMemory函数传过去的是值,不是地址,也就是说,在GetMemory函数内对str的任何操作都不会影响到str,所以str还是NULL指针,所以下一步对str也就是NULL指针解引用会出错,也就打印不出来。再来看GetMemory函数内部,开辟之后没有进行是否为NULL指针进行检查,也没进行释放,造成内存泄露。
4.2 题目2:
char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); }
运行Test函数会有怎样的结果?
这段代码同样有问题,虽然这回把地址传给了str,但是形参出了函数就会被销毁,所以造成的后果就是str是野指针。
4.3 题目3:
void GetMemory(char** p, int num) { *p = (char*)malloc(num); } void Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); }
运行Test函数会有怎样的结果呢?
这段代码的问题就是没有对动态开辟的内存释放造成内存泄漏。
4.4 题目4:
void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); if (str != NULL) { strcpy(str, "world"); printf(str); } }
这段代码的问题就是
1.编码习惯不好,free完之后,没有置为NULL指针。
2.free之后动态开辟的空间就被释放,无法访问,有地址没空间,str是野指针。
五、C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
1.栈区(stack):在执行函数时,函数内局部变量的存储单位都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。
2.堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
六、柔性数组
6.1 柔性数组的概念
在C99标准中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
譬如:
typedef struct st_type { int i; int a[];//柔性数组成员 }type_a;
6.2柔性数组的特点
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
3.包含柔性数组成员的结构用,malloc()函数进行内存的动态分配,并且分配的
的内存应该大于结构的大小,以适应柔性数组的预期大小。
对于第二点我们可以代码验证一下:
6.3 柔性数组的使用
我们通常在给包含柔性数组的结构体开辟动态内存时,习惯柔性数组需要多大我们就给多大空间。
譬如:
typedef struct st_type { int i; int a[];//柔性数组成员 }type_a; int main() { type_a* ptr = (type_a*)malloc(sizeof(type_a) + 10*sizeof(int));//后面40个字节是给arr提供的 if (ptr == NULL) { printf("%s\n", strerror(errno)); } ptr->i = 100; int j = 0; for (j = 0; j < 10; j++) { ptr->a[j] = j; printf("%d ", ptr->a[j]); } //柔性数组是malloc(动态内存开辟)的,可以realloc调整大小,体现了柔性 type_a* ps = (type_a*)realloc(ptr, sizeof(type_a) + 20*sizeof(int)); if (ps != NULL) { ptr = ps; ps = NULL; } // free(ptr); ptr = NULL; return 0; }
通过上图我们可以发现柔性数组使得我们对于结构体使用的更加方便。
当然各位同学可能还是不太体会到柔性数组的好处,我们对于上述代码同样,再使用普通方式开辟空间。
struct S { int n; int* arr;//采用这样的结构,不是柔性数组 }; int main() { printf("%d\n", sizeof(struct S));//大小为8 struct S* ps=(struct S*)malloc(sizeof(struct S));// if (ps == NULL) { printf("%s\n", strerror(errno)); } ps->n = 100; ps->arr = (int*)malloc(40); if (ps->arr == NULL) { printf("%s\n", strerror(errno)); return 1; } //使用了 int i = 0; for (i = 0; i < 10; i++) { ps->arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } //释放 //释放两次 //注意先释放数组,再释放结构体 //扩容 int* ptr = (int*)realloc(ps->arr, 80); if (ptr == NULL) { return 1; }//两次malloc,还要注意free顺序,比较麻烦,还容易出现内存泄露 //还有malloc次数越多,内存碎片越多,使得程序变得繁杂 free(ps->arr); free(ps); ps = NULL; return 0; }
通过对比我们发现第二种方式的坏处:
1、两次malloc开辟空间,产生较多的内存碎片,使得程序的运行效率受到影响,不如方法一采用柔性数组。
2、free两次,还要注意顺序,先free掉结构体里面的数组,不能先free掉结构体,这样会导致丢失动态开辟的数组,造成内存泄露,所以不仅要free两次,还要注意先后,比较繁琐还容易出错。