当前位置:   article > 正文

动态内存管理-c语言

动态内存管理-c语言

目录

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.为什么要有动态内存分配

  1. int val = 20;//
  2. 在栈空间上开辟四个字节
  3. char arr[10] = {0};//
  4. 在栈空间上开辟
  5. 10
  6. 个字节的连续空间

但是上述的开辟空间的⽅式有两个特点:

• • 空间开辟⼤⼩是固定的。

* *数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知 道,那数组的编译时开辟空间的⽅式就不能满⾜了。 C语⾔引⼊了动态内存开辟,让程序员⾃⼰可以申请和释放空间,就⽐较灵活了。

2.malloc函数和free函数

malloc

是c语言用于动态内存开辟的函数

函数原型

void* malloc (size_t size);

说明:malloc向系统申请分配指定size个字节的内存空间。返回类型是void *类型。void *类型表示未确定类型的指针。C、C++规定,void *类型可以强制转换为任何其他类型的指针

这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。

******#include<stdlib.h>//malloc头文件

• • • • 如果开辟成功,则返回⼀个指向开辟好空间的指针。

*******如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针,因此malloc的返回值⼀定要做检查。

*******void* 所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候⾃⼰来绝定。

********如果参数 size 为0,malloc的⾏为是标准是未定义的,取决于编译器

栗子

因为malloc()返回值是返回函数开辟空间的地址,类型是void,所以p应该是要用void * p来接收,但是void类型的指针,是不能进行解引用的,所以这个方法行不通。

解决办法

int* p = (int *)malloc();

要把malloc返回值进行强制转换成你要开辟空间用于什么数据类型的类型。

比如这里我需要int类型的内存,所以这里要转换成int类型。

重要一步检查是否内存开辟成功也莫忘:

如果开辟失败,则返回⼀个 返回值的类型是 NULL 指针

  1. int* p = (int *)malloc(40);
  2. if (p == NULL)
  3. {
  4. perror("malloc");//报错
  5. return 1;
  6. }
  7. //使用开好的内存
  8. for (int i = 0; i < 10; i++)
  9. {
  10. /**p = i;
  11. p++;*/
  12. //上面写法,写到后面。p位置不好找回来
  13. *(p + i) = i;//这样写p就不会动了
  14. }
  15. for (int i = 0; i < 10; i++)
  16. {
  17. printf("%d ", *(p+i));
  18. }

free


函数原型

void free (void* ptr);

free参数是动态内存申请好返回的指针变量。

函数free,专⻔是⽤来做动态内存的释放和回收的

**free函数必须和malloc函数同时使用不然会报错

**free函数无法释放栈内存的变量

**free只能释放动态内存申请的空间

栗子

  1. int* p = (int *)malloc(40);
  2. if (p == NULL)
  3. {
  4. perror("malloc");//报错
  5. return 1;
  6. }
  7. //使用开好的内存
  8. for (int i = 0; i < 10; i++)
  9. {
  10. /**p = i;
  11. p++;*/
  12. //上面写法,写到后面。p位置不好找回来
  13. *(p + i) = i;//这样写p就不会动了
  14. }
  15. for (int i = 0; i < 10; i++)
  16. {
  17. printf("%d ", *(p+i));
  18. }
  19. free(p);
  20. p = NULL;//因为释放了内存,但是 p指向的位置还是内存的起始地址,避免成为野指针,要指向空。

3.calloc和***realloc***

3.1calloc函数

原型如下:

void* calloc (size_t num, size_t size);

****函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

***malloc与calloc区别就是malloc不会初始化,而calloc初始化为0
***第二就是,参数不一样,calloc可以输入指定个数,和大小

栗子

  1. //使用calloc函数进行动态内存申请
  2. int* p = (int*)calloc(10, sizeof(int));

3.2***recalloc***

realloc函数可以做到对动态开辟内存⼤小调整;

realloc函数的出现让动态内存管理更加灵活。

函数原型

 void* realloc (void* ptr, size_t size);

realloc分为三种情况,

第一种情况

申请空间失败;

第二种情况

申请成功,但开辟空间的时候后面没有足够的空间,在这种情况下,realloc函数会在内存的堆区重新找一个空间(这个空间满足新的申请空间大小需求),同时会把旧的数据拷贝到新的空间,然后释放旧的空间,同时返回新的空间的起始地址。

第三种情况

也是成功情况,但是这次后面的空间是足够的情况,可以直接进行扩大,扩大空间后,直接返回旧的空间的起始地址。

  1. //要用一个临时变量存储申请的空间,防止申请失败而置为null
  2. int* p = (int*)calloc(10, sizeof(int));
  3. int ptr = (int*)reallco(p, 10 * sizeof(int));
  4. if (ptr != NULL)
  5. {
  6. p = ptr;
  7. }
  8. else
  9. {
  10. perror("realloc");
  11. return 1;
  12. }

recalloc模拟实现calloc函数

  1. int* p = (int *)realloc(NULL,40);
  2. if (p == NULL)
  3. {
  4. perror("malloc");//报错
  5. return 1;
  6. }
  7. //使用开好的内存
  8. for (int i = 0; i < 10; i++)
  9. {
  10. /**p = i;
  11. p++;*/
  12. //上面写法,写到后面。p位置不好找回来
  13. *(p + i) = i;//这样写p就不会动了
  14. }
  15. for (int i = 0; i < 10; i++)
  16. {
  17. printf("%d ", *(p+i));
  18. }
  19. free(p);
  20. p = NULL;//因为释放了内存,但是 p指向的位置还是内存的起始地址,避免成为野指针,要指向空。

4.六大常⻅的动态内存的错误

4.1对NULL指针的解引⽤操作

没有进行判断是否申请内存成功而直接使用

  1. int a = 20;
  2. int* p = (int*)malloc(40);
  3. *p = a;

4.2对动态开辟空间的越界访问

  1. int* p = (int*)malloc(40);
  2. //类似于数组的越界访问
  3. if (NULL == p)
  4. {
  5. exit(EXIT_FAILURE);
  6. }
  7. for (int i = 0; i <= 10; i++)
  8. {
  9. //i<=10,就是越界访问了,因为申请的大小才10个int大小
  10. //i<10才行。
  11. *(p + i) = i;
  12. }
  13. free(p);
  14. p = NULL;

 4.3对⾮动态开辟内存使⽤free释放

  1. int a = 0;
  2. int* p = (int*)malloc(40);
  3. if (NULL == p)
  4. {
  5. exit(EXIT_FAILURE);
  6. }
  7. //这里就出错了
  8. p = &a;
  9. //p指向a地址,此时就丢失了开辟的空间,形成空间泄露的情况。
  10. free(p);
  11. p = NULL;

 4.4使⽤free释放⼀块动态开辟内存的⼀部分

  1. //只释放一部分的动态内存,也会错误,free只能放内存的首地址。
  2. int* p = (int*)malloc(40);
  3. if (NULL == p)
  4. {
  5. exit(EXIT_FAILURE);
  6. }
  7. p ++;
  8. free(p);
  9. p = NULL;

 4.5对同⼀块动态内存多次释放

  1. int *p = (int *)malloc(100);
  2. free(p);
  3. free(p);//重复释放

4.6 动态开辟内存忘记释放(内存泄漏)

:malloc/calloc/realloc 申请的空间如果不主动释放,出了作用域是不会销毁的
释放的方式:
1.free主动释放

2.直到程序结束,才由操作系统回收

  1. void test()
  2. {
  3. int *p = (int *)malloc(100);
  4. if(NULL != p)
  5. {
  6. *p = 20;
  7. }
  8. }
  9. int main()
  10. {
  11. test();
  12. while(1);
  13. }

 5.动态内存经典笔试题分析

5.1题目一

  1. void GetMemory(char* p)
  2. {
  3. p = (char*)malloc(100);//而这里申请的空间是p的,
  4. //后面这个函数运行结束,p就会消失,申请的内存也就会泄露,导致程序崩溃
  5. }
  6. int main()
  7. {
  8. char* str = NULL;
  9. GetMemory(str);//传过去的是str,并不是地址
  10. strcpy(str, "hello world");//str并没有申请有内存,却直接进行解引用,会导致程序崩溃。
  11. printf(str);
  12. free(str);
  13. str = NULL;
  14. }

str并没有申请有内存,却直接进行解引用,会导致程序崩溃。

正确解法

  1. void GetMemory(char** p)
  2. {
  3. *p = (char*)malloc(100);
  4. }
  5. int main()
  6. {
  7. char* str = NULL;
  8. GetMemory(&str);//这里传地址过去就好了
  9. strcpy(str, "hello world");
  10. printf(str);
  11. free(str);
  12. str = NULL;
  13. }

5.2题目二

  1. char* GetMemory(void)
  2. {
  3. char p[] = "hello world";//这里创建的内存,如果函数运行完成就会自动销毁了
  4. return p;//那么此时p就是野指针。
  5. }
  6. void Test(void)
  7. {
  8. char* str = NULL;
  9. str = GetMemory();
  10. printf(str);
  11. }
  12. int main()
  13. {
  14. Test();
  15. return 0;
  16. }

static用法讲解

正确解法

  1. char* GetMemory(void)
  2. {
  3. //static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长。
  4. static char p[] = "hello world";//这里创建的内存,如果函数运行完成就会自动销毁了
  5. return p;//那么此时p就是野指针。
  6. }
  7. void Test(void)
  8. {
  9. char* str = NULL;
  10. str = GetMemory();
  11. printf(str);
  12. }
  13. int main()
  14. {
  15. Test();
  16. return 0;
  17. }

5.3题目三

  1. void GetMemory(char **p, int num)
  2. {
  3. *p = (char *)malloc(num);
  4. }
  5. void Test(void)
  6. {
  7. char *str = NULL;
  8. GetMemory(&str, 100);
  9. strcpy(str, "hello");
  10. printf(str);
  11. }

这里就是缺少了free销毁申请的内存,会造成内存泄露

正确解法

  1. void GetMemory(char** p, int num)
  2. {
  3. *p = (char*)malloc(num);
  4. if (*p == NULL)
  5. {
  6. return 0;
  7. }
  8. }
  9. void Test(void)
  10. {
  11. char* str = NULL;
  12. GetMemory(&str, 100);
  13. strcpy(str, "hello");
  14. printf(str);
  15. free(str);
  16. str = NULL;
  17. }
  18. int main()
  19. {
  20. printf("韦坤四战\n");
  21. Test();
  22. return 0;
  23. }

5.4题目四

  1. void Test(void)
  2. {
  3. char *str = (char *) malloc(100);
  4. strcpy(str, "hello");
  5. free(str);
  6. if(str != NULL)
  7. {
  8. strcpy(str, "world");
  9. printf(str);
  10. }
  11. }

这里错的是free释放掉str后,并没有手动把str指向null;

正确解法

  1. void Test(void)
  2. {
  3. char* str = (char*)malloc(100);
  4. strcpy(str, "hello");
  5. free(str);
  6. str = NULL;
  7. if (str != NULL)
  8. {
  9. strcpy(str, "world");
  10. printf(str);
  11. }
  12. }

6. 柔性数组

6.1什么是柔性数组

满足下面三个条件即是柔性数组:

1.在结构体中

2.结构体中最少两个成员,并且它是最后一个成员

3.是一个未知大小的数组

柔性数组的特点:

 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。

sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。

包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤ ⼩,以适应柔性数组的预期⼤⼩。

栗子

  1. struct st_type
  2. {
  3. int i;
  4. /*1.在结构体中
  5. 2.结构体中最少两个成员,并且它是最后一个成员
  6. 3.是一个未知大小的数组*/
  7. int a[0];//柔性数组成员
  8. }type_a;
  9. int main()
  10. {
  11. printf("%d\n", sizeof(type_a));//只会计算i的大小为4,数组a[0]是未知的无法计算。
  12. return 0;
  13. }

6.2柔性数组的使用

  1. //申请动态内存
  2. struct st *ps = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int));//10 * sizeof(int)这里就是给柔性数组开的内存。
  3. if (ps = NULL)
  4. {
  5. perror("ps");
  6. return 0;
  7. }
  8. //赋值
  9. ps->i = 10;
  10. for (int i = 0; i < 10; i++)
  11. {
  12. ps->a[i] = i;
  13. }

6.3柔性数组的优势

  1. //柔性数组的优势
  2. //就是可以随时扩大柔性数组的大小。
  3. //申请动态内存
  4. struct st *ps = (struct st*)malloc(sizeof(struct st) + 10 * sizeof(int));//10 * sizeof(int)这里就是给柔性数组开的内存。
  5. if (ps = NULL)
  6. {
  7. perror("ps");
  8. return 0;
  9. }
  10. //赋值
  11. ps->i = 10;
  12. for (int i = 0; i < 10; i++)
  13. {
  14. ps->a[i] = i;
  15. }
  16. struct st *ptr = (struct st*)realloc(ps, sizeof(struct st) + 15 * sizeof(int));
  17. if (ps = NULL)
  18. {
  19. perror("ps");
  20. return 0;
  21. }
  22. else
  23. {
  24. ps = ptr;
  25. }

第⼀个好处是:⽅便内存释放

 如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤ ⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能 指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返 回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。

第⼆个好处是:这样有利于访问速度. 连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你 跑不了要⽤做偏移量的加法来寻址

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/209712
推荐阅读
相关标签
  

闽ICP备14008679号