【C语言】小王带您轻松实现动态内存管理(简单易懂)

简介: 【C语言】小王带您轻松实现动态内存管理(简单易懂)

前言

我们已经掌握的内存开辟的方法有两种

int a = 10;      //在栈空间上开辟4个字节的空间

int a[10] = {0};  //在栈空间上开辟40个字节的连续空间

这些开辟方式都有两个共同的特点:

1.空间开辟大小是固定的

2.数组在申明的时候,必须指定数组的长度,它需要的内存在编译的时候分配

我们为什么要实现动态管理内存呢,这又什么作用呢?

我们对于空间的需求不仅仅只是上面两种,有时候我们到底需要多少空间,需要运行之后才能知道,这个时候就需要动态开辟内存空间,即动态内存函数就诞生了!

一、动态内存函数有那些?

1.malloc和free

2.calloc

3.realloc

1.1 malloc和free

malloc是C语言提供的一个动态内存开辟的函数:

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

1.如果开辟成功,则返回一个指向开辟好空间的指针。

2.如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

3.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

4.如果参数 size 0malloc的行为是标准是未定义的,取决于编译器。

void*的返回类型,使用的时候根据情况强制类型转换

C语言还提供free函数,专门是用于做动态内存的释放和回收的,函数原型如下:

free函数是用来释放动态开辟的内存:

1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。(会报错)

2.如果参数 ptr NULL指针,则函数什么事都不做。

图文演示:

头文件要加上 malloc.h

代码演示:

1. int main()
2. {
3.  int num = 0;
4.  scanf("%d", &num);
5.  //int arr[num] = { 0 };   num 在 [] 中
6.  //VS 不支持这样,但是可以使用动态内存函数,实现动态数组
7.  int* ptr = (int*)malloc(sizeof(int) * num);
8.  if (NULL == ptr) {//进行判断是否创建成功
9.    perror("malloc::ptr");  
10.   }
11.   else {
12.     for (int i = 0; i < 10; i++) {
13.       *(ptr + i) = i;
14.     }
15.     for (int i = 0; i < 10; i++) {
16.       printf("%d ", *(ptr + i));
17.     }
18.     free(ptr);  //使用free函数释放动态申请的ptr
19.     ptr = NULL;  //将ptr  free之后,置为NULL,防止野指针非法访问
20.   }
21. 
22.   return 0;
23. }

而且malloc函数创建的空间不会进行初始化,里面存放的是随机值,如图

1.2 calloc

calloc函数也是C语言提供的,用来动态内存分配,原型如下:

calloc函数介绍:

1.函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0

2.与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

实操图文分析:

代码演示:

1. int main()
2. {
3.  int num = 0;
4.  int* ptr = (int*)calloc(10, sizeof(int));//使用calloc函数
5.  for (int i = 0; i < 10; i++) {
6.      *(ptr + i) = i;
7.    }
8.    for (int i = 0; i < 10; i++) {
9.      printf("%d ", *(ptr + i));
10.     }
11.     free(ptr);//free 动态申请的ptr
12.     ptr = NULL;//置为NULL,防止野指针越界访问
13.   return 0;
14. 
15. }

对于calloc动态申请的空间是否每一个字节都变为0呢?我们来看下图

这也是calloc和malloc函数的最大的区别,是否自动初始化,前者有,后者无

1.3 realloc

realloc也是C语言提供的动态内存申请函数,使得动态内存管理更加灵活。本质是可以对已经动态申请过的空间进行增容,是更加灵活的。

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。

函数原型如下,并对两个形参ptr和size进行分析:

如上图:

1.ptr可以为NULL,相当于malloc一个新的空间,ptr是要调整的内存地址

2.size同样可以为0,则返回值取决于特定的库实现:它可能是空指针,也可能是不应取消引用的其他位置。size是调整之后的大小

3.返回值为调整之后的内存起始位置。

4.这个函数调整原内存空间大小的基础上,还会将原来的数据移动到新空间。

1.3.1 realloc调整内存空间的时候有两种情况:

第一种情况:当原有空间之后的内存空间足够的时候

第二种情况:当原有空间之后的内存空间不够时

如图所示:

因为这两种情况是随机发生的,不能控制必须使用哪一种,所以我们就要小心一个事情,不要用原来动态开辟的变量ptr来直接接收realloc,应该创建临时变量接收,先判空,之后再赋值给ptr

代码图示:

可以自行测试:

1. int main()
2. {
3.  int* p = (int*)malloc(sizeof(int)*10);
4.  if (p == NULL) {
5.    perror("malloc::p");
6.  }
7.  else {
8.    printf("%p\n", p);
9.  }
10.   int* ptr = (int*)realloc(p, sizeof(int) * 20);//创建临时变量
11. //如果使用 int* p = (int*)realloc(p,....这样的话如果创建失败,返回NULL,
12. //这样的话p的内容就没有了,所以创建临时变量ptr,然后下面判空之后可以交换
13.   if (NULL == ptr) {
14.     perror("realloc::ptr");
15.   }
16.   else {
17.     p = ptr;
18.     ptr = NULL;
19.     printf("%p\n", p);
20.   }
21. 
22.   free(p);
23.   p = NULL;
24.   return 0;
25. }

二、常见动态内存错误(案例分析)

2.1 对于NULL指针的解引用操作

意思就是要学会使用动态内存函数的时候吗,要进行判空,不然谁知道有没有问题NULL

1. int main()
2. {
3.  int* p = (int*)malloc(sizeof(int) * 10);
4.  *p = 10;//这个时候谁知道p是不是NULL,如果是NULL,那么这就是非法访问,是错误
5.  free(p);
6. return 0;
7. }
8.

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

就是说,开辟多少空间就是多少空间,不能越过这个字节数的界限访问空间外的地址

1. int main()
2. {
3.  int* p = (int*)malloc(sizeof(int) * 10);
4.  if (NULL == p) {
5.    perror("malloc::p");
6.  }
7.  else {
8.    for (int i = 0; i < 100; i++) {
9.      *(p + i) = i + 1;//当i等于10的时候就开始越界访问
10.     }
11.     for (int i = 0; i < 11; i++) {
12.       printf("%d ", *(p + i));
13.     }
14.     free(p);
15.     p = NULL;
16.   }
17.   return 0;
18. }

和数组一样,不要越界,不需要多想什么额外的东西

2.3 对非动态开辟内存使用free释放

free可以放置NULL进去,不会报错,但是不能放非动态开辟的内存,会报错

图示分析free函数:

代码演示:

1. int main()
2. {
3.  int* p = (int*)malloc(sizeof(int) * 10);
4.  int a = 10;
5.  free(&a);//非动态内存开辟的,会报错
6. //free(NULL);  //没有什么反应,程序正常
7.  return 0;
8. }
9.

2.4 使用free释放了动态开辟内存的一部分

就是说如果动态开辟内存之后的p指针的位置发生改变的话再去释放free(p)只是释放一部分

代码演示:

1. //举例
2. int main()
3. {
4.  int* p = (int*)malloc(sizeof(int) * 10);
5.  p++;
6.  free(p);//这个的时候p向右移动一个整型字节空间,再进行释放,那么先前那个空间就没被释放
7.  return 0;
8. }

2.5 对同一块动态内存进行多次释放

多次释放会报错的

图示:

2.6 动态开辟空间忘记释放(内存泄漏)

所以我们要养成当一个动态空间不用的时候就free他,放置内存泄露

代码演示:

1. int main()
2. {
3.  //test();
4.  while (1) {
5.    malloc(1);//一直申请就是不释放
6.  }
7. }

三、练习题

3.1 第一个

1. void GetMemory(char *p)
2. {
3.  p = (char *)malloc(100);
4. }
5. void Test(void)
6. {
7. char *str = NULL;
8. GetMemory(str);
9. //改为传递地址就可以或者就是用str接收
10. //str= GetMemory(str);//实际上用临时变量接收更好
11. strcpy(str, "hello world");
12. printf(str);
13. //用完释放
14. //free(str);
15. //str=NULL;
16. }

1.传值操作,就算p申请了空间也不会使得str发生改变,所以str依旧是NULL,不能有strcpy

2.内存泄漏, GetMemory(str);未释放p的空间

3.2 第二个

1. char *GetMemory(void)
2. {
3. //修改为:
4. //static char p[] = "hello world";
5. char p[] = "hello world";
6. return p;
7. }
8. void Test(void)
9. {
10. char *str = NULL;
11.  str = GetMemory();
12. printf(str);
13. }

典型的返回栈地址问题,p数组是局部变量 ,确实是返回了p的地址给str,但是GetMemory函数结束之后,数组p的空间就没有,再访问p的地址(printf(str))就会非法访问

3.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(str);
12. //str=NULL;
13. }

没有释放str动态开辟的空间,没有free(str),str=NULL

3.4 第四个

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

在使用str之前就释放了str申请的空间,释放之后str!=NULL,保留原来地址,str这个时候已经是野指针了(因为没有了对相应空间的访问权限),之后确实是输出了world,但是从if语句就已经错误了,置为str=NULL 就可以了

总结

本文主要是对于malloc、calloc、realloc、free函数的介绍和使用细节的说明,还有一些关于动态内存管理的函数,学会了这些,对于以后数据结构的内容会更加得心应手,所以希望大家能多多支持,接下来,下一章,我们跟大家讲解一下,文件管理的内容。学会了就可以更新通讯录啦!!!

相关文章
|
3月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
45 3
|
1月前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
52 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
1月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
69 6
|
2月前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
60 6
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
186 13
|
2月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
78 11
|
2月前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
2月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
68 11
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
71 1