C语言动态内存管理(一)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: C语言动态内存管理(一)

本篇重点内容;

1.为什么存在动态内存分配?

2.动态内存函数的介绍

✨malloc

✨free

✨calloc

✨realloc

3.常见的动态内存错误

4.几个经典的笔试题

5.柔性数组


一、为什么存在动态内存分配?

我们已经掌握的内存开辟方式有:

创建一个变量或者创建一个数组

但是上面这样的开辟空间的方式有两个特点:

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

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

实际上,对于空间的需求,有时候我们需要的空间大小在程序运行时才能知道,那么上述数组的开辟方式就不能满足了。

这时就需要使用动态内存开辟。

二、动态内存函数的介绍

1.malloc

malloc头文件<stdlib.h>

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

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

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

(2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。(注意开辟失败是因为malloc不会无节制的开辟下去)

对malloc的返回值进行判断:

if(p= =Null){
printf(“%s\n”,strerror(errno));
return 1;
}else{

}

注意:

a. strerror(errno):将错误码对应的错误信息的字符串地址返回

b. strerror 头文件<string.h>

c. errno 头文件<errno.h>

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

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

(5)malloc的使用:

//malloc的使用
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  //张三
  //申请
  int* p = (int*)malloc(20);
  //对malloc的返回值进行判断
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 5; i++) {
    p[i] = i + 1;//相当于*(p+i)=i+1;
  }
  for (i = 0; i < 5; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间并置空
  free(p);
  p = NULL;
  return 0;
}

(6)堆默认给malloc出来的空间初始化

如果不给malloc空间放值,直接打印出来空间的值是一些随机值

//堆默认给malloc出来的空间初始化
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  //张三
  //申请
  int* p = (int*)malloc(20);
  //对malloc的返回值进行判断
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //直接打印
  int i = 0;
  for (i = 0; i < 5; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间并置空
  free(p);
  p = NULL;
  return 0;
}

2.free

free头文件<stdlib.h>

专门是用来做动态内存的释放和回收的,函数原型如下:

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

(1)要释放哪块空间就把哪块空间的起始地址传给free

(2)ptr必须指向的是动态开辟的空间,才能用free释放

(3)free空指针,则什么事情也不做

(4)free前,指针ptr本来指向一个地方,释放ptr指针之后,ptr里面还存着一个地址,如果不置空,这个地址被别有用心的人利用,ptr就是野指针了。

所以:释放指针完毕后,将指针主动置为空指针,这样就再也找不到刚刚开辟的空间了,避免出现野指针

3.calloc

calloc函数也用来动态内存分配。原型如下:

callloc(num个元素,每个元素的字节大小)

calloc会开辟一个像数组一样的内存块

注意点:

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

(2)calloc与malloc开辟空间的不同点是calloc会在返回地址之前把申请的空间的每个字节初始化为0

(3)calloc与malloc的对比:

a. 参数不一样

b. 都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0

如果要初始化,就使用calloc

如果不需要初始化,就使用malloc

(4)举例使用calloc:

//举例使用calloc
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main() {
  int* p = (int*)calloc(10, sizeof(int));
  //判断是否开辟成功
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用空间
  int i = 0;
  for (i = 0; i < 10; i++) {
    printf("%d ", p[i]);
  }
  //释放
  free(p);
  p = NULL;
  return 0;
}

4.realloc

realloc可以对空间进行大小调整

函数原型如下:

realloc(要调整的空间的内存地址,调整后空间的新大小)

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

注意点

(1)realloc调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

(2)realloc在调整内存空间时存在两种情况:

第一种情况:原有空间之后有足够大的空间,realloc后面追加的新的空间是不会赋初值的,只会把旧的数据拷贝下来,此时realloc返回的是旧地址

第二种情况:原有空间之后没有足够大的空间,扩展的方法是在堆空间上另找一个更大的连续空间来使用,将原来的数据拷贝到新的空间,释放旧的空间,此时函数返回一个新的内存地址

(3)堆中开辟的内存不一定是连续的

(4)如果realloc调整的空间要比原空间小, 多余的空间会全释放不使用,还给操作系统

(5)举例使用realloc:

//举例使用realloc:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  int* p = (int*)malloc(20);
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 5; i++) {
    p[i] = i + 1;
  }
  int* ptr = (int*)realloc(p, 40);
  if (ptr != NULL) {
    p = ptr;
  }
  else {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //调整空间后使用
  for (i = 0; i < 10; i++) {
    p[i] = i + 1;
  }
  for (i = 0; i < 10; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间
  free(p);
  p = NULL;
  return 0;
}

三、常见的动态内存错误

1.对NULL指针的解引用操作

malloc/calloc动态开辟失败,没有检查返回值

举例说明:

解析:

如果动态开辟内存失败,p指针就是空指针,当i=0时,对p指针(空指针)解引用出问题,不能对空指针解引用

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

动态开辟申请了多少字节就访问多少字节,不要越界

举例说明:

解析:

总共开辟了20个字节的整型空间,在访问的时候访问了10个整型(40个字节)的数据,越界访问

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

在内存中开辟的空间如果不在堆上,不能用free释放

举例说明:

解析:

函数里面的局部变量或者数组都是在栈上开辟的空间,不能用free释放

4.使用free释放动态开辟内存的一部分

free释放空间必须提供要释放空间的起始位置

举例说明:

解析:动态开辟的空间的起始位置的指针在使用的过程中被修改,不在再指向起始位置,使用完这块空间用free回收的时候,无法提供动态开辟内存空间的起始位置,程序出错

5.对同一块动态内存多次释放

(1)malloc / calloc动态开辟的空间是放过一次就不能再释放了。如果在第一次释放完之后置为空指针,第二次释放相当于释放空指针没有问题
(2)与别的函数交互时,不了解函数内部,很可能在函数内部已经释放过一次,又回到主函数中释放一次导致对同一块动态内存多次释放出错;又或者在函数的内部没有释放动态开辟的空间,回到主函数中也没有释放,导致内存泄漏,程序出错

举例说明:

解析:

连续重复释放同一块动态内存出现问题

释放后置空,再释放是没有问题的,如下:

6.动态开辟内存忘记释放(内存泄露)

指的是动态开辟内存既不使用free释放,也不结束程序时造成的

✨重点✨

(1)malloc / calloc /realloc所申请的空间如果不想使用,需要用free释放。如果不释放,程序结束后,也会由操作系统回收。
(2)如果不使用free释放,程序也不结束,就会造成内存泄漏

意思是不用不还,程序也不结束,别人也无法利用这块空间,等于这块空间浪费掉了

(3)回收内存的两种方式:
a. free掉
b. 程序结束后,由操作系统回收

举例说明:

解析:

申请的20个字节的地址放在p指针里面,p是个局部变量,函数一旦返回,p就销毁不见了,并没有通过p把这20个字节的地址返回来。所以出了函数,来到主函数内部,主函数根本找不到这块内存空间在哪里,想释放都释放不了,造成了内存泄漏

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

你好,我是AI助理

可以解答问题、推荐解决方案等