【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

news2024/11/19 7:31:27

一、动态内存分配

定义动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中,动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样,需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小

目前掌握的两种开辟内存的方式:

// 在栈空间上开辟四个字节
int val = 20; 

// 在栈空间上开辟10个字节的连续空间
char arr[10] = {0}; 
上述的开辟空间的方式有两个特点:

  1. 空间开辟大小固定的。
  2. 数组在声明时必须指定数组的长度,在编译时会分配其所需要的内存空间

存在动态内存开辟的原因:对于空间的需求,不仅仅是上述的情况。有时我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时我们就需要动态内存开辟来解决问题。


二、动态内存函数的介绍

1、malloc 函数

void* malloc(size_t size);
  • malloc 是 C 语言提供的一个动态内存开辟的函数,该函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
  1. 如果开辟成功,则返回一个指向开辟好空间的指针
  2. 如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查
  3. 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 为 0malloc 的行为是标准是未定义的,取决于编译器。

cplusplus.com/reference/cstdlib/malloc/?kw=malloc


2、free 函数

void free (void* ptr);
  • free 函数用来释放动态开辟的内存。
  1. 如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
  2. 如果参数 ptr 是 NULL 指针,则 free 函数将不会执行任何动作。

注意

  1. 使用完之后一定要记得使用 free 函数释放所开辟的内存空间。
  2. 使用指针指向动态开辟的内存,使用完并 free 之后一定要记得将其置为空指针

cplusplus.com/reference/cstdlib/free/


【演示】

#include <stdio.h>
#include <stdlib.h>
 
int main() 
{
    int arr[10]; // 开辟10个整型空间
 
    int* p = (int*)malloc(10*sizeof(int)); // 动态开辟10个大小为int的空间
 
    if (p == NULL) // 判断p指针是否为空
    {
        perror("main"); // main: 错误信息
        return 0;
    }

    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", p[i]);
    }
 
    // 回收空间
    free(p); // 释放p指针所指向的动态内存
    p = NULL; // 需要手动置为空指针
 
    return 0;
}

【1】为什么 malloc 前面要进行强制类型转换呢?

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

为了和 int* p 类型相呼应,所以要进行强制类型转换。如果把强制转换删掉,其实也不会有什么问题。但是因为有些编译器要求强转,所以最好进行一下强转,避免不必要的麻烦。


【2】为什么 free 之后,一定要把 p 置为空指针?

因为 free 之后那块开辟的内存空间已经不存在了,它的功能只是把开辟的空间回收掉,但是 p 仍然还指向那块内存空间的起始位置,这并不合理。所以需要使用 p = NULL 把它置成空指针。


3、calloc 函数

void* calloc(size_t num, size_t size);
  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0

cplusplus.com/reference/cstdlib/calloc/

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int* p = (int*)calloc(10, sizeof(int)); // 开辟10个大小为int的空间,40
    if (p == NULL)
    {
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }

    free(p);
    p = NULL;
 
    return 0;
}

 

结论:说明 calloc 会对内存进行初始化,把空间的每个字节初始化为 0 。如果我们对于申请的内存空间的内容,要求其初始化,我们就可以使用 calloc 函数来轻松实现。


4、realloc 函数

void* realloc (void* ptr, size_t size);
  • realloc 函数,让动态内存管理更加灵活。用于重新调整之前调用 malloccalloc 所分配的 ptr 所指向的内存块的大小,可以对动态开辟的内存进行大小的调整

  1. ptr 为指针要调整的内存地址
  2. size 调整之后的新大小
  3. 返回值为调整之后的内存起始位置,请求失败则返回空指针
  4. realloc 函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

  • realloc 在调整内存空间的是存在三种情况:
  1. 原有空间之后有足够大的空间。
  2. 原有空间之后没有足够大的空间。
  3. realloc 有可能找不到合适的空间来调整大小。

第三种情况,如果 realloc 找不到合适的空间,就会返回空指针。如果想让它增容,它却存在返回空指针的危险,怎么办?

不要拿指针直接接收 realloc,可以使用临时指针判断一下。

#include <stdio.h>
#include <stdlib.h>
 
int main() 
{
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i)  = 5;
    }

    // 此时,这里需要 p 指向的空间更大,需要 20 个int的空间
    int* ptmp = (int*)realloc(p, 20*sizeof(int));

    // 如果ptmp不等于空指针,再把p交付给它
    if (ptmp != NULL)
    {
        p = ptmp;
    }
 
    free(p);
    p = NULL;
}

当要调整的内存地址为 NULL 时,realloc 的功能相当于 malloc。 

int* p = (int*)realloc(NULL, 40); // 这里功能类似于malloc,就是直接在堆区开辟40个字节

三、常见的动态内存错误

1、NULL指针的解引用操作

// error - 错误演示
#include <stdlib.h>
#include <stdio.h>

void test()
{
    int* p = (int*)malloc(9999999999);
    *p = 20; // 对空指针进行解引用操作,非法访问内存
    free(p);
    return 0;
}
// 正确代码
#include <stdlib.h>
#include <stdio.h>
 
int main()
{
    int* p = (int*)malloc(9999999999);

    if (p == NULL) // 对malloc函数的返回值做判空处理
    {
        perror("main")
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i; // 对空指针进行解引用操作,非法访问内存
    }
 
    return 0;
}

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

堆上开辟的空间是有范围的。

// error
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int* p = (int*)malloc(10*sizeof(int)); // 申请10个整型的空间
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    for (int i = 0; i < 40; i++) // 越界访问 - 指针p只管理10个整型的空间,根本无法访问40个
    {
        *(p + i) = i;
    }
 
    free(p);
    p = NULL;
 
    return 0;
}

注意:为了防止越界访问,使用空间时一定要注意开辟的空间大小。 


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

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int arr[10] = {0};
    int* p = arr;
 
    free(p); // 使用free释放非动态开辟的空间 - error
    p = NULL;
 
    return 0;   
}

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

void test()
{
    int *p = (int *)malloc(100);
    p++;
    free(p); // p不再指向动态内存的起始位置
}

        此时 free(p) 就会出问题,释放的是后面的空间。不能从一块动态开辟的内存空间的某一部分释放,必须从头开始释放!

        这么写会导致 p 只释放了后面的空间,并不知道这块空间的起始位置,这样会存在内存泄露的风险。

注意释放内存空间的时候一定要从头开始释放。


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

void test()
{
    int *p = (int *)malloc(100);
    free(p);
    // p = NULL;
    free(p);//重复释放
}

应该在第一次释放后紧接着将 p 置为空指针。


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

#include <stdio.h>
#include <stdlib.h>
 
void test()
{
    int* p = (int*)malloc(100);
    if (p == NULL)
    {
        return;
    }
    // 这里忘记释放了
}
 
int main()
{
    test();
    
    free(p); // 此时释放不了了,并不知道这块空间的起始位置在哪
    p = NULL;
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
注意 动态开辟的空间一定要释放,并且正确释放。

C / C++ 中程序内存区域划分

C/C++程序内存分配的几个区域:
  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收 。分配方式类似于链表。
  3. 数据段(静态区)(static):存放全局变量静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被 static 修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以 生命周期变长

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/919732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JVM——类加载与字节码技术—字节码指令

2.字节码指令 2.1 入门 jvm的解释器可以识别平台无关的字节码指令&#xff0c;解释为机器码执行。 2a b7 00 01 b1 this . init&#xff08;&#xff09; return 准备了System.out对象&#xff0c;准备了参数“hello world”,准备了对象的方法println(String)V&#xff…

车载充电器日本PSE认证申请资料和流程

PSE认证是日本针对电气用品的一个强制性安全认证。 日本的采购商在购进商品后一个月内必须向日本经济产业省&#xff08;METI&#xff09;注册申报。在日本市场上销售的DENAN目录范围内的电气电子产品都必须通过PSE认证。日本DENAN将电气电子产品分为两类&#xff1a;特定电气…

CSS background 背景

background属性为元素添加背景效果。 它是以下属性的简写&#xff0c;按顺序为&#xff1a; background-colorbackground-imagebackground-repeatbackground-attachmentbackground-position 以下所有示例中的花花.jpg图片的大小是4848。 1 background-color background-col…

【面试专题】Spring篇①

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;Java面试专题 目录 1.你知道 Spring 框架中有哪些重要的模块吗&#xff1f; 2. 谈谈你对 IOC 的认识。 3. 谈谈你对 AOP 的认识。 4.在实际写代码时&#xff0c;有没有用到过 AOP&#xff1f;用…

数字化技术无限延伸,VR全景点亮智慧生活

随着互联网的发展&#xff0c;我们无时无刻不再享受着互联网给我们带来的便利&#xff0c;数字化生活正在无限延伸&#xff0c;各行各业也开始积极布局智能生活。要说智慧生活哪个方面应用的比较多&#xff0c;那应该就是VR全景了&#xff0c;目前VR全景已经被各个行业广泛应用…

PyPI 如何在本地配置访问不同的仓库地址

PyPI 是可以在本地计算机上进行配置来访问远程的仓库地址的。 检查配置文件 检查配置文件使用的命令为&#xff1a; pip config -v list 通过上面的配置文件&#xff0c;我们可以知道 Python 的 PyPI 的配置文件信息。 上面图片显示的是配置文件的扫描路径。 修改 pip.ini…

flink checkpoint时exact-one模式和atleastone模式的区别

背景&#xff1a; flink在开启checkpoint的时候有两种模式可以选择&#xff0c;exact-one和atleastone模式&#xff0c;那么这两种模式有什么区别呢&#xff1f; exact-one和atleastone模式的区别 先说结论&#xff1a;exact-one可以完全做到状态的一致性&#xff0c;而atle…

gorm中正确的使用json数据类型

一、说明 1、JSON 数据类型是 MySQL 5.7.8 开始支持的。在此之前&#xff0c;只能通过字符类型&#xff08;CHAR&#xff0c;VARCHAR 或 TEXT &#xff09;来保存 JSON 文档。现实中也很多人不会采用json的存储方式&#xff0c;直接定义一个字符类型,让前端转换传递进来,返回给…

Spring Boot多环境指定yml或者properties

Spring Boot多环境指定yml或者properties 文章目录 Spring Boot多环境指定yml或者properties加载顺序配置指定某个yml 加载顺序 ● application-local.properties ● application.properties ● application-local.yml ● application.yml application.propertes server.port…

2023前端面试笔记 —— CSS3

系列文章目录 内容链接2023前端面试笔记HTML52023前端面试笔记CSS3 文章目录 系列文章目录前言一、CSS选择器的优先级二、通过 CSS 的哪些方式可以实现隐藏页面上的元素三、px、em、rem之间有什么区别&#xff1f;四、让元素水平居中的方法有哪些五、在 CSS 中有哪些定位方式六…

什么是遗传算法(Genetic Algorithm,简称 GA)?

目录 一、遗传算法介绍二、遗传算法应用场景三、遗传算法具体案列1、求解旅行商问题&#xff08;TSP 问题&#xff09;2、求解一个矩阵中的最大值3、基于遗传算法的图像压缩方法 四、遗传算法重要意义五、生物进化与遗传算法之间的关系 一、遗传算法介绍 遗传算法&#xff08;…

Sketch 98 中文版-mac矢量绘图设计

Sketch是一款专为Mac操作系统设计的矢量图形编辑软件&#xff0c;被广泛应用于UI/UX设计、网页设计、移动应用设计等领域。Sketch提供了各种工具和功能&#xff0c;包括绘图、图形设计、排版等&#xff0c;可以帮助设计师轻松地创建高质量的矢量图形和模型。Sketch的主要特点包…

自动气象站丨自动观测和记录气象信息

所谓自动气象站&#xff0c;是指无需人工就能自动检测多种气象要素&#xff0c;并将数据实时发送给观测中心的气设备。它是填补小区域气象探测数据空白的重要手段。自动气象站主要由传感器、数据采集器、太阳能供电系统、立杆组成&#xff0c;可以监测风速、风向、降雨量、空气…

[MyBatis系列①]增删改查

目录 1、基础回顾 2、例子引入 3、映射文件 4、增删改查 4.1、add 4.2、delete 4.3、update 4.4、check 5、核心配置文件 5.1、properties 5.2、typeAliases 5.2.1、自定义 5.2.2、⭐MyBatis自带 5.3、environments 5.3.1、environment 5.3.2、transactionMana…

DataWhale 机器学习夏令营第三期——任务二:可视化分析

DataWhale 机器学习夏令营第三期 学习记录二 (2023.08.23)——可视化分析1.赛题理解2. 数据可视化分析2.1 用户维度特征分布分析2.2 时间特征分布分析 DataWhale 机器学习夏令营第三期 ——用户新增预测挑战赛 学习记录二 (2023.08.23)——可视化分析 2023.08.17 已跑通baseli…

论文解读:Image-Adaptive YOLO for Object Detection in Adverse Weather Conditions

发布时间&#xff1a;2022.4.4 (2021发布&#xff0c;进过多次修订) 论文地址&#xff1a;https://arxiv.org/pdf/2112.08088.pdf 项目地址&#xff1a;https://github.com/wenyyu/Image-Adaptive-YOLO 虽然基于深度学习的目标检测方法在传统数据集上取得了很好的结果&#xf…

【C++数据结构】二叉搜索树

【C数据结构】二叉搜索树 目录 【C数据结构】二叉搜索树二叉搜索树概念二叉搜索树操作二叉搜索树的查找二叉搜索树的插入二叉搜索树的删除二叉搜索树的实现二叉搜索树的应用二叉搜索树的性能分析 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.8.22 前言&#xff1a;二…

MySQL数据库管理操作

MySQL常用的数据类型 int&#xff1a;整型 用于定义整数类型的数据float&#xff1a;单精度浮点4字节32位准确表示到小数点后六位double&#xff1a;双精度浮点8字节64位char&#xff1a;固定长度的字符类型用于定义字符类型数据。Char如果存入数据的实际长度比指定长度要…

前端工程化概述

软件工程定义&#xff1a;将工程方法系统化地应用到软件开发中 前端发展历史 前端工程化的发展历史可以追溯到互联网的早期阶段&#xff0c;随着前端技术的不断演进和互联网应用的复杂化&#xff0c;前端工程化也逐渐成为了前端开发的重要领域。以下是前端工程化的主要发展里程…

诚迈科技子公司智达诚远与Unity中国达成合作,打造智能座舱新时代

2023 年 8 月 23 日&#xff0c;全球领先的实时 3D 引擎 Unity 在华合资公司 Unity 中国举办发布会&#xff0c;正式对外发布 Unity 引擎中国版——团结引擎&#xff0c;并带来专为次世代汽车智能座舱打造的团结引擎车机版。发布会上&#xff0c;诚迈科技副总裁、诚迈科技子公司…