当前位置:   article > 正文

C语言之动态内存管理

C语言之动态内存管理

1.引言,需要动态内存管理的原因:在之前所学的内容中,定义一个整型或者数组,都是向内存申请一片空间,但是这些申请方式一旦申请好空间,大小就无法调整,那么这时候就需要一种灵活地申请空间的功能。

2.相关函数:(1)malloc函数:用于动态内存开辟的函数。

函数原型:void* malloc(size_t size);  里面的size是需要申请的空间,单位是字节,最后返回申请出来的空间的起始地址,但是指针类型是void,原因malloc函数并不知道是开辟的空间用处是什么,是存整型还字符型,所以最后的决定权在使用者手中,其实实际使用中,使用者就已经想好申请的内存空间用于什么了,所以通常需要强制类型转换。最后再做个判断,如果开辟失败了,malloc会返回空地址,就报错:perror("malloc"),开辟成功就使用空间了。举个例子:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. int* p = (int*)malloc(20);
  6. if (p == NULL)
  7. {
  8. perror("malloc");
  9. }
  10. //使用:
  11. int i = 0;
  12. for (i = 0; i < 5; i++)
  13. {
  14. *(p + i) = i + 1;
  15. }
  16. return 0;
  17. }

调试一下:

最后也是成功地把值赋进了p地址。 

多提一嘴,malloc函数申请的空间在堆区,还有free、calloc、realloc所关联的内存空间都是在堆区上的。

(2)free函数:使用完内存中如果不使用了就可以使用free,用来释放和回收内存空间。

函数原型:void free(void* ptr); 参数是一个指针变量,所以要传一个要释放的内存空间的地址,而且是起始地址。但是,如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。如果传的是空指针的话,那么free函数就什么都不做。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. int* p = (int*)malloc(20);
  6. if (p == NULL)
  7. {
  8. perror("malloc");
  9. }
  10. //使用:
  11. int i = 0;
  12. for (i = 0; i < 5; i++)
  13. {
  14. *(p + i) = i + 1;
  15. }
  16. //释放内存:
  17. free(p);
  18. p = NULL;
  19. return 0;
  20. }

而且是这一整块空间的使用权限都还给操作系统,一旦free执行后,p就是一个野指针了,最后就赋一个空指针。

(3)calloc函数:功能是为num个大小为size的元素开辟一块空间并且把每个字节初始化为0。

函数原型:void* calloc(size_t num, size_t size);

  1. //向内存申请5个整型的空间
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. /*int* p = (int*)malloc(5 * sizeof(int));
  6. if (p == NULL)
  7. {
  8. perror("malloc");
  9. }*/
  10. int* p = (int*)calloc(5 , sizeof(int));
  11. if (p == NULL)
  12. {
  13. perror("malloc");
  14. }
  15. //使用:
  16. int i = 0;
  17. for (i = 0; i < 5; i++)
  18. {
  19. *(p + i) = i + 1;
  20. }
  21. //释放内存:
  22. free(p);
  23. return 0;
  24. }

(4)realloc:对开辟出来的内存空间进行大小的调整。

函数原型:void* realloc(void* ptr, size_t size);

ptr是要调整的内存的地址,size是调整之后的大小,返回值是调整之后的内存的起始地址 

但是在调整大小的时候会有两种情况:

已经开辟出来的20个字节里后面刚好还有多于20个字节的空间尚未分配。

后面的空间不够用了。

情况一就是直接在后面续上了,情况二是重新在堆区找一块满足大小要求的空间,再把原来的数据重新拷贝一份放到新的空间里面,然后把原来的那块给释放掉,返回新的空间地址。 

但如果是空间调整失败的话就会返回空指针。最后完善一下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main()
  4. {
  5. /*int* p = (int*)malloc(5 * sizeof(int));
  6. if (p == NULL)
  7. {
  8. perror("malloc");
  9. }*/
  10. int* p = (int*)calloc(5 , sizeof(int));
  11. if (p == NULL)
  12. {
  13. perror("malloc");
  14. }
  15. //使用:
  16. int i = 0;
  17. for (i = 0; i < 5; i++)
  18. {
  19. *(p + i) = i + 1;
  20. }
  21. //希望将内存调整为40个字节:
  22. int* ptr = (int*)realloc(p, 40);
  23. if (ptr != NULL)
  24. {
  25. p = ptr;
  26. //使用四十个空间的字节:
  27. int i = 0;
  28. for (i = 5; i < 10; i++)
  29. {
  30. *(p + i) = i + 1;
  31. }
  32. for (i = 0; i < 10; i++)
  33. {
  34. printf("%d ", *(p + i));
  35. }
  36. //释放内存:
  37. free(p);
  38. p = NULL;
  39. }
  40. else perror("realloc");
  41. return 0;
  42. }

其实realloc函数不仅有上面的用法,还可以实现和malloc函数一样的功能,如果把需要传递的地址换成空指针,realloc就会开辟一块新空间。

下面演示空间开辟失败的案例:

3.常见的动态内存错误:

(1)对空指针的解引用操作:在使用时应该对返回的指针值进行判断,如果不做判断直接使用,就有可能会对空指针进行解引用操作。

(2)对动态开辟空间的越界访问: 

  1. int main()
  2. {
  3. int i = 0;
  4. int* p = (int*)malloc(10 * sezeof(int));
  5. if (NULL == p)
  6. {
  7. exit(EXIT_FAILURE);
  8. }
  9. for (i = 0; i <= 10; i++)
  10. {
  11. *(p + i) = i + 1;
  12. }
  13. free(p);
  14. p = NULL;
  15. return 0;
  16. }

当i=10时,p指向了第十一个,这样就会越界,类似于数组。

(3)对非动态开辟的内存释放:就像是下面这样:

  1. int main()
  2. {
  3. int a = 10;
  4. int* p = &a;
  5. free(p);
  6. p = NULL;
  7. return 0;
  8. }

这样写是错误的。

(4)使用free释放动态内存空间的一部分。就比如:

  1. int main()
  2. {
  3. int* p = (int*)malloc(100);
  4. {
  5. return 1;
  6. }
  7. int i = 0;
  8. for (i = 0; i < 5; i++)
  9. {
  10. *p = i + 1;
  11. p++;
  12. }
  13. free(p);
  14. return 0;
  15. }

p++已经指向了偏移原来位置五个整型的位置了,然后现在free的意思是从这个位置开始释放内存,不包含前面的内存。这种行为是不允许的。

(5)对同一块动态内存多次释放:就是写了两次free(p),除非这两次释放中间有给p赋值为空指针,free(NULL)是什么都不做。

(6)动态开辟内存最后忘记释放:

  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. return 0;
  14. }

开辟这个内存既不使用也不释放,程序运行到while时就会死循环,然后一直占有着这块内存不还给操作系统,这样会造成内存泄漏的情况。

其实malloc和realloc申请的内存如果不想使用的话可以使用free函数,如果没有使用free来释放当程序运行结束时也会由操作系统回收的。

4.柔性数组:结构体中最后一个成员而且还是数组且没有指定大小

(1)特点:sizeof返回的结构体大小不包含柔性数组的内存

(2)包含柔性数组的结构体成员用malloc函数进行动态的内存分配并且分配的内存应该大于结构体以适应柔性数组的预期大小。具体例子:

  1. struct S
  2. {
  3. int n = 0;
  4. int arr[];
  5. };
  6. int main()
  7. {
  8. malloc(sizeof(struct S) + 10 * sizeof(int));
  9. return 0;
  10. }

假设你希望这个柔性数组的大小有十个整型元素,那么在动态内存分配的时候就应该加上期望的柔性数组的大小,最后用一个结构体的指针接收。这个时候动态内存分配好了之后就可以使用realloc去调整,realloc可以使这个内存变成变短,前面的那个整型大小又不变,所以后面的那个数组就也会跟着变长变短,就实现了柔性。

(2)使用:

  1. struct S
  2. {
  3. int n = 0;
  4. int arr[];
  5. };
  6. int main()
  7. {
  8. struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
  9. if (ps == NULL)
  10. {
  11. perror("malloc");
  12. return 1;
  13. }
  14. ps->n = 100;
  15. int i = 0;
  16. for (i = 0; i < 10; i++)
  17. {
  18. ps->arr[i] = i;
  19. }
  20. struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 15 * sizeof(int));
  21. if (ptr != NULL)
  22. {
  23. ps = ptr;
  24. }
  25. //......
  26. //释放:
  27. return 0;
  28. }

还有一个方式,将数组转化为指针,然后先结构体整体开辟,再开辟指针里面的空间。

第一次开辟成功了的话就开辟arr的,需要十个整型,就用malloc函数开辟。最后就是:

  1. struct S
  2. {
  3. int n = 0;
  4. int* arr;
  5. };
  6. int main()
  7. {
  8. struct S* ps = (struct S*)malloc(sizeof(struct S));
  9. if (ps == NULL)
  10. {
  11. perror("malloc");
  12. return 1;
  13. }
  14. ps->arr = (int*)malloc(5 * sizeof(int));
  15. if (ps->arr == NULL)
  16. {
  17. return 1;
  18. }
  19. ps->n = 100;
  20. int i = 0;
  21. for (i = 0; i < 10; i++)
  22. {
  23. ps->arr[i] = i;
  24. }
  25. int* ptr = (int*)realloc(ps->arr, 15 * sizeof(int));
  26. if (ptr != NULL)
  27. {
  28. ps->arr = ptr;
  29. }
  30. //......
  31. //释放:
  32. return 0;
  33. }

大概就是这样。

全部内容,over。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号