常见的动态内存错误
1.对NULL指针的解引用操作
int main() { int* p = (int*)malloc(100000000000000000); //错误:未对malloc函数的返回值做判断,就直接解引用了 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } return 0; }
修改:
int main() { int* p = (int*)malloc(100000000000000000); //错误:未对malloc函数的返回值做判断,就直接解引用了 if (p == NULL) { perror("main"); return 1; } int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } return 0; }
2.对动态开辟空间的越界访问
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; //越界访问 for (i = 0; i < 40; i++) { *(p + i) = i; } free(p); p = NULL; return 0; }
修改:
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; //越界访问 for (i = 0; i < 10; i++) { *(p + i) = i; } free(p); p = NULL; return 0; }
3.对非动态开辟内存使用free释放
#include <stdio.h> #include <stdlib.h> int main() { int arr[10] = { 0 }; int* p = arr; //使用 //释放 free(p); p = NULL; return 0; }
4.使用free释放一块动态开辟内存的一部分
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(10 * sizeof(int)); if (p == NULL) { return 1; } int i = 0; for (i = 0; i < 5; i++) { *p++ = i; } free(p);//p不再指向动态内存的起始位置 // p = NULL; }
注意:free释放的动态内存空间一定是该空间的起始地址,否则程序将会崩溃。
5.对同一块动态内存多次释放
#include <stdio.h> #include <stdlib.h> int main() { int* p = (int*)malloc(100); //使用 //释放 free(p); //p=NULL;及时置空 //再一次释放 free(p); return 0; }
注意:释放动态开辟的内存后,一定要将指向该空间的指针置为空指针NULL。否则就很容易出现对同一块动态内存多次释放的问题,导致程序崩溃。
6.动态开辟内存忘记释放(内存泄漏)
#include <stdio.h> #include <stdlib.h> void test() { int* p = (int*)malloc(100); if (p == NULL) { return; } //使用 } int main() { test(); //... return 0; }
上面的代码运行时,不会爆出什么错误。但是忘记释放动态开辟的内存,就会导致内存泄漏,这样你的内存就会越来越少。这是非常严重的一个问题,所以使用完动态开辟的内存之后,一定要记得释放该内存并及时置空。
动态开辟的空间的两种回收方式:
- 主动free
- 程序结束
几道经典的笔试题
题目一
#include <stdio.h> #include <stdlib.h> #include <string.h> void GetMemory(char* p) { p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { Test(); return 0; }
输出结果:
代码分析:
修改:
#include <stdio.h> #include <stdlib.h> #include <string.h> char* GetMemory(char* p) { p = (char*)malloc(100); return p; } void Test(void) { char* str = NULL; str = GetMemory(str); strcpy(str, "hello world"); printf(str); //printf("hello world"); //传的是字符h的地址 free(str); str = NULL; } int main() { Test(); return 0; }
void GetMemory(char** p) { *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); //printf("hello world"); //传的是h的地址 free(str); str = NULL; } int main() { Test(); return 0; }
题目二
#include <stdio.h> #include <stdlib.h> #include <string.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; }
输出结果:
代码分析:
题目三
#include <stdio.h> #include <stdlib.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; }
输出结果:
代码分析:
修改:
#include <stdio.h> #include <stdlib.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); free(str); str = NULL; } int main() { Test(); return 0; }
题目四
#include <stdio.h> #include <stdlib.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; }
输出结果:
代码分析:
修改:
#include <stdio.h> #include <stdlib.h> #include <string.h> void Test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); //动态内部释放后要及时置空 str = NULL; if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { Test(); return 0; }
C/C++程序的内存开辟
C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
有了这幅图,我们就可以更好的理解static关键字修饰局部变量的例子了。
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
举个例子:
struct S { int n; int arr[];//大小是未知的 };
有些编译器会报错无法编译可以改成:
struct S { int n; int arr[0];//大小是未知的 };
1.柔性数组的特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
代码示例:
#include <stdio.h> struct S { int n; int arr[0];//大小是未知的 }; int main() { struct S s = { 0 }; printf("%d\n", sizeof(s)); return 0; }
输出结果:
2.柔性数组的使用
使用方法一:
#include <stdio.h> #include <stdlib.h> struct S { int n; int arr[0];//大小是未知的 }; int main() { //期望arr的大小是10个整型 struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int)); ps->n = 10; int i = 0; for (i = 0; i < 10; i++) { ps->arr[i] = i; } //增容 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int)); if (ptr != NULL) { ps = ptr; } //使用 //释放 free(ps); ps = NULL; return 0; }
使用方法二:
#include <stdio.h> #include <stdlib.h> struct S { int n; int* arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) return 1; ps->n = 10; ps->arr = (int*)malloc(10 * sizeof(int)); if (ps->arr == NULL) 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, 20 * sizeof(int)); if (ptr != NULL) { ps->arr = ptr; } //使用 //释放需要两次 free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }
注意:上面的两种使用方法,本人推荐使用第一种。因为如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉,出错的可能性会更小。同时第一种方法访问内存的速度也会更高,因为连续的内存有益于提高访问速度,也有益于减少内存碎片。
以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个赞支持一下!