前言
动态内存管理,是C语言三大重点之一。
我们知道,申请一块内存我可以通过创建一个变量来完成,其实也可以有另一种方式,叫做动态内存申请,下面我们来简单看一下吧:
1.动态内存的意义?
在C语言中,申请内存的一般方式是创建一个指定变量比如int,char,当然可以是自定义变量类型比如说struct int arr[5]等等…
但是这样申请一块内存是固定的,局限性较大
为了提高程序员在使用C语言编写程序时候申请内存空间的灵活性,动态内存的概念被引入到了C语言中。
2.malloc and free
2.1malloc
malloc函数是一个专门用来进行动态内存申请的库函数,其基本语法如下:
注:
- .如果malloc函数成功申请到一块空间,会返回申请空间的起始地址,如果申请失败,则会返回一个空指针,因此当我们使用指针变量接收malloc返回值时候,要进行检验,防止出现野指针问题。
- 一般而言,存储空间分以下几种,而对于malloc函数申请的内存空间是存储在堆区的
- malloc函数申请的空间默认为随机值,申请后并不做任何处理。
2.2free
free也是一个专门针对于动态内存管理的库函数,其基本语法如下:
注:
- free完成申请的动态内存空间后,应及时把指向动态内存空间的指针变量置为NULL,防止指针变量成为野指针。
3.calloc、realloc函数
这两个函数也是用来开辟或者调整内存空间的。
3.1calloc
calloc函数也是一个库函数,其功能是开辟动态内存空间并且重置其中的内容的,其基本语法如下:
3.2realloc
realloc也是一个库函数,不过该函数侧重于申请动态内存空间的调整,语法如下:
- 但这里realloc函数的返回值需要重点强调一下,realloc函数的返回值规则如下:
4.常见的动态管理错误
-对空指针解引用操作
//1.典例:对空指针解引用操作 int main() { int* p = (int*)malloc(100); /*if (p == NULL) { //提示报错 perror("malloc"); return 1; } */ *p = 20;//p有可能是null指针 //释放 free(p); p = NULL; return 0; }
- 对动态开辟空间的越界访问
//2.典例:对动态开辟空间的越界访问: int main() { int* p = NULL; p = malloc(40); if (p == NULL) { printf("p为空指针\n"); return 1; } //使用 int i = 0; for (i = 0; i <= 10; i++) { *(p + i) = i;//循环到第11次就会越界修改 } //释放空间并将指针置为空 free(p); p = NULL; return 0; }
- 对非动态开辟内存使用free释放
//3. 典例:对非动态开辟内存使用free释放 int main() { int a = 10; int* p = (int*)malloc(100); if (p == NULL) { return 1; } //... p = &a;//p指向的空间不再是堆区,而是栈区! free(p); p = NULL; return 0; }
- 使用free释放了一部分动态内存空间
int main() { int a = 10; int*p = (int*)malloc(40); if(p==NULL) { return 1; } //... p++; free(p); p = NULL; return 0; }
-对同一块内存的多次重复free
-动态开辟内存忘记释放,引起内存泄露问题
//典型内存泄露问题: void test() { int* p = (int*)malloc(100); if (NULL != p) { *p = 20; } } int main() { test(); while (1); }
//有时候及时free了,可能会存在逻辑错误引起内存泄漏 void test() { int* p = (int*)malloc(100); if (p != NULL) { return; } free(p); p = NULL; } int main() { test(); while (1); }
5.关于动态内存管理的面试题
提示:请先自己分析,没有结论或者想验证一下答案看解析。
下面四道面试题都是有问题的,请自行看代码说问题,解析是说的什么问题。
面试题1:
//面试题1: 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; }
解析:
上面函数关系是这样的:
在第一步中,Getmemory传进去的是实参str的内容,即NULL,形参p接收到了实参的内容空指针,并且拿到了开辟100字节的首地址,但是这跟str没什么关系,str没有拿到100字节的首地址,并且出了Getmemory函数,p的空间就被收回了,申请的100字节空间是堆空间没有被收回,所以也无法释放。
面试题2:
//面试题2: char* GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { Test(); return 0; }
解析:这个题目确实把p的地址返回去了,但是由于出了作用域,char p中的hello world 的空间被收回,属于越界访问了。
面试题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); }
解析:这个代码是有问题的,没有进行free释放堆内存空间,造成内存泄漏问题,释放空间之后还要记得把指向堆空间的指针置为NULL。
面试题4:
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; }
解析:
问题1:free空间之后,又要利用空间
问题2:没有及时把指针置为NULL,造成野指针问题
问题3:前面第一次使用strcpy已经默认str不是空指针,到后面又要if检查,逻辑不清晰,修改的话可以把free后面把str置为空指针即可。
6.柔性数组
柔性数组的概念:
柔性数组是指在一个结构体中,在结构体最后一个成员为未定义长度大小的数组,并且该结构体中还有其他类型的变量。
构成柔性数组的条件有下面几点:
1.在一个结构体中
2.柔性数组是最后一个成员
3.位置大小的数组
4.结构体中至少有一个其他类型的成员
柔性数组的特点:
1.sizeof计算的结构体大小不包括柔性数组的大小
2.包含柔性数组的结构体空间要使用malloc尽心申请空间
柔性数组的代码示例:
//柔性数组: int main() { //结构体类型的创建 struct st { int n; char ch; int arr[];//假设刚开始我要存10个int类型的数字,后面空间不够了需要拓展到15个int类型的数字 }; //为结构体变量申请一块堆空间 struct st* ps = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int)); //检查ps是否为空指针 if (ps == NULL) { perror("malloc"); return 1; } //如果ps没有问题,我可以为结构体赋值了(使用) ps->ch = 'a'; ps->n = 66; int i = 0; for (i = 0; i < 10; i++) { ps->arr[i] = i; } //后来我发现柔性数组10个空间不够了,我要存储15个int的内容 //没关系,我们再申请一些内存空间就好了 struct st* ptr =(struct st*)realloc(ps, sizeof(struct st)+ 15 * sizeof(int));//因为realloc可能会申请失败,我不敢直接让ps接收地址 if (ptr != NULL) { //没有问题的情况下,我们可以把realloc申请的空间地址给ps复制过来 ps = ptr; } else//如果申请失败,那我先报错并终止程序 { perror(realloc); return 1; } //我们继续使用新申请的空间哈哈 for (i = 10; i < 15; i++) { ps->arr[i] = i; } //我们打印一下看一下结果: for (i = 0; i < 15; i++) { printf("%d ", ps->arr[i]); } printf("\nn = %d\n", ps->n); printf("ch = %c\n", ps->ch); //使用完了,我们也得记得释放空间啊,防止出现内存泄露问题 free(ps); ps = NULL; ptr = NULL; return 0; }
模拟柔性数组功能的一种代码:
//模拟柔性数组的方法: int main() { struct st { int n; char ch; int* p; }; //首先我得给这个结构体来申请堆空间把结构体放下 struct st* ps = (struct st*)malloc(sizeof(struct st)); //我需要检查一下这个结构体指针变量是否为空 if (ps == NULL) { perror("malloc"); return 1; } //既然没问题,那我可以给这个结构体变量复制了吧 ps->ch = 'g'; ps->n = 66; ps->p = (int*)malloc(10 * sizeof(int));//我们让结构体后面那个指针指向一个空间 //老规矩,用一个指针指向一块堆空间的时候我们首先需要检查一下是否有无问题 if (ps->p == NULL) { perror("malloc-2"); return 1; } //使用 int i = 0; for (i = 0; i < 10; i++) { ps->p[i] = i; } //后来我发现,使用了10个int类型的字节不够。还想要五个 //那继续申请咯 struct st* str = (struct st*)realloc(ps->p,15 * sizeof(int)); if (str != NULL) { ps->p = str; } else { perror("realloc"); return 1; } for (i = 10; i < 15; i++) { ps->p[i] = i; } for (i = 0; i < 15; i++) { printf("%d ", ps->p[i]); } printf("\nn = %d\n", ps->n); printf("ch = %c\n", ps->ch); //free free(str); free(ps); str = NULL; ps = NULL; return 0; }
简单说一下,柔性数组是比模拟柔性数组的使用更好一下的,具体为什么可以自行探索。(百度啊)
结语:
动态内存管理也是C语言重点之一,希望务必重视。
我们说,指针,结构体,动态内存是学习数据结构的基础。