【C语言】动态内存管理详解

文章目录

  • 前言
  • 动态内存管理出现的原因
  • malloc函数和free函数
    • 函数原型
    • 使用
  • calloc函数和realloc函数
    • 函数原型
    • 使用
  • 动态内存使用中容易出现的错误
  • 柔性数组
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:
动态内存管理是C语言中一项重要的编程任务,它使得程序在运行时能够灵活地分配和释放内存,更好地适应不同的运行条件。通过动态内存管理,我们可以实现更高效、更灵活的内存使用方式,但也需要谨慎处理,避免内存泄漏和其他潜在问题。本篇博客将深入探讨C语言中的动态内存管理,探索其原理、使用方法以及注意事项。


提示:以下是本篇文章正文内容,下面案例可供参考

动态内存管理出现的原因

考虑以下场景,展示了动态内存管理解决实际问题的情况:

#include <stdio.h>
#include <stdlib.h>int main() {int *dynamicArray;int size;// 用户输入数组大小printf("Enter the size of the array: ");scanf("%d", &size);// 动态分配内存dynamicArray = (int *)malloc(size * sizeof(int));// 检查内存是否分配成功if (dynamicArray == NULL) {printf("Memory allocation failed.\n");return 1;}// 用户输入数组元素printf("Enter %d integers:\n", size);for (int i = 0; i < size; i++) {scanf("%d", &dynamicArray[i]);}// 打印数组内容printf("Array elements: ");for (int i = 0; i < size; i++) {printf("%d ", dynamicArray[i]);}// 释放动态分配的内存free(dynamicArray);return 0;
}

在这个例子中,用户输入数组大小,程序根据用户输入动态分配了一个整数数组。用户可以根据实际需求输入不同大小的数组,而不受固定大小的限制。动态内存的分配和释放使得程序更加灵活,能够适应不同规模的数据处理需求。

这是动态内存管理的一个实际应用,通过动态内存的灵活性,程序可以根据运行时的需求动态调整内存的大小,提高了程序的适应性和可扩展性。

malloc函数和free函数

函数原型

malloc函数:void* malloc (size_t size);

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

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自行决定。
  4. 如果参数size为0,malloc的行为是标准未定义的,取决于编译器。

free函数:void free (void* ptr);

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

  1. 如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的。
  2. 如果参数ptr是NULL指针,则函数什么也不做。

使用

动态内存管理示例:malloc 和 free 函数

在C语言中,mallocfree 函数的使用对于动态内存的分配和释放至关重要。下面通过一个详细的例子来说明它们的作用和使用。

#include <stdio.h>
#include <stdlib.h>// 定义结构体用于存储学生信息
struct Student {char name[50];int age;
};int main() {// 用户输入学生数量int numStudents;printf("Enter the number of students: ");scanf("%d", &numStudents);// 动态分配内存以存储学生信息struct Student *students = (struct Student *)malloc(numStudents * sizeof(struct Student));// 检查内存是否分配成功if (students == NULL) {printf("Memory allocation failed.\n");return 1;}// 用户输入学生信息for (int i = 0; i < numStudents; i++) {printf("Enter name for student %d: ", i + 1);scanf("%s", students[i].name);printf("Enter age for student %d: ", i + 1);scanf("%d", &students[i].age);}// 打印学生信息printf("\nStudent Information:\n");for (int i = 0; i < numStudents; i++) {printf("Student %d:\n", i + 1);printf("Name: %s\n", students[i].name);printf("Age: %d\n", students[i].age);printf("\n");}// 释放动态分配的内存free(students);students = NULL;return 0;
}

步骤解析:

  1. 用户输入学生数量: 通过 scanf 函数获取用户输入的学生数量。

  2. 动态分配内存: 使用 malloc 函数分配足够存储学生信息的内存块。类型转换 (struct Student *) 是为了将返回的 void 指针转换为结构体指针。

  3. 检查内存分配是否成功: 使用条件语句检查 malloc 是否成功分配了内存。如果分配失败,打印错误消息并退出程序。

  4. 用户输入学生信息: 通过循环,用户逐个输入学生的姓名和年龄。

  5. 打印学生信息: 通过循环,打印用户输入的学生信息。

  6. 释放动态分配的内存: 使用 free 函数释放先前分配的内存,确保在不再需要时及时释放,防止内存泄漏。

这个例子演示了如何使用 mallocfree 动态地管理内存,使程序更具灵活性,能够适应不同数量的学生信息。

calloc函数和realloc函数

函数原型

void* calloc (size_t num, size_t size);

  1. 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.
  2. 与函数malloc的区别只在于calloc会在返回地址之前把申请到的空间,每个字节都初始化为0.

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

  • realloc函数的出现让动态内存管理更加灵活。
  • 有时候我们会发现过去申请的空间太小了,或者发现申请的空间过大了,那么为了合理的使用内存空间,我们一定会对内存的大小做灵活的调整。那么realloc函数就可以做到对动态开辟的内存大小做调整。
  • ptr是要调整的内存地址
  • size调整之后新大小
  • 返回值为调整之后的内存起始位置
  • 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到的空间。
  • realloc在调整内存空间的时候存在两种情况:
    • 情况1:原有空间之后有足够大的空间
    • 原有空间之后没有足够大的空间

使用

动态内存管理示例:calloc 和 realloc 函数

在C语言中,除了 mallocfree 外,还有 callocrealloc 函数用于更灵活地进行内存分配和重新分配。下面通过一个详细的例子来说明它们的作用和使用。

#include <stdio.h>
#include <stdlib.h>// 定义结构体用于存储学生信息
struct Student {char name[50];int age;
};int main() {// 用户输入学生数量int numStudents;printf("Enter the number of students: ");scanf("%d", &numStudents);// 使用 calloc 分配并初始化存储学生信息的内存块struct Student *students = (struct Student *)calloc(numStudents, sizeof(struct Student));// 检查内存是否分配成功if (students == NULL) {printf("Memory allocation failed.\n");return 1;}// 用户输入学生信息for (int i = 0; i < numStudents; i++) {printf("Enter name for student %d: ", i + 1);scanf("%s", students[i].name);printf("Enter age for student %d: ", i + 1);scanf("%d", &students[i].age);}// 打印学生信息printf("\nStudent Information:\n");for (int i = 0; i < numStudents; i++) {printf("Student %d:\n", i + 1);printf("Name: %s\n", students[i].name);printf("Age: %d\n", students[i].age);printf("\n");}// 用户输入新的学生数量int newNumStudents;printf("Enter the new number of students: ");scanf("%d", &newNumStudents);// 使用 realloc 函数重新分配内存,适应新的学生数量students = (struct Student *)realloc(students, newNumStudents * sizeof(struct Student));// 检查内存重新分配是否成功if (students == NULL) {printf("Memory reallocation failed.\n");return 1;}// 用户输入新学生信息for (int i = numStudents; i < newNumStudents; i++) {printf("Enter name for new student %d: ", i + 1);scanf("%s", students[i].name);printf("Enter age for new student %d: ", i + 1);scanf("%d", &students[i].age);}// 打印更新后的学生信息printf("\nUpdated Student Information:\n");for (int i = 0; i < newNumStudents; i++) {printf("Student %d:\n", i + 1);printf("Name: %s\n", students[i].name);printf("Age: %d\n", students[i].age);printf("\n");}// 释放动态分配的内存free(students);students = NULL;return 0;
}

步骤解析:

  1. 用户输入学生数量: 通过 scanf 函数获取用户输入的学生数量。

  2. 使用 calloc 分配并初始化内存块: 使用 calloc 函数分配足够存储学生信息的内存块,并将内存初始化为零。类型转换 (struct Student *) 是为了将返回的 void 指针转换为结构体指针。

  3. 检查内存是否分配成功: 使用条件语句检查 calloc 是否成功分配了内存。如果分配失败,打印错误消息并退出程序。

  4. 用户输入学生信息: 通过循环,用户逐个输入学生的姓名和年龄。

  5. 打印学生信息: 通过循环,打印用户输入的学生信息。

  6. 用户输入新的学生数量: 通过 scanf 函数获取用户输入的新学生数量。

  7. 使用 realloc 函数重新分配内存: 使用 realloc 函数重新分配内存,适应新的学生数量。如果新的数量比原来的多,额外的内存空间将被初始化为零。

  8. 检查内存重新分配是否成功: 使用条件语句检查 realloc 是否成功重新分配了内存。如果重新分配失败,打印错误消息并退出程序。

  9. 用户输入新学生信息: 通过循环,用户逐个输入新学生的姓名和年龄。

  10. 打印更新后的学生信息: 通过循环,打印更新后的学生信息。

  11. 释放动态分配的内存: 使用 free 函数释放先前分配的内存,确保在程序结束时释放所有内存。

这个例子展示了 callocrealloc 的使用,以及如何在运行时适应不同数量的学生信息。

动态内存使用中容易出现的错误

在动态内存管理中,一些常见的错误可能导致程序运行时出现问题,如内存泄漏、访问越界等。下面详细介绍这些错误,并给出相应的例子:

  1. 内存泄漏: 忘记释放已分配的内存,导致程序运行时持续占用内存而不释放。

    // 错误示例
    int *arr = (int *)malloc(5 * sizeof(int));
    // 忘记释放内存
    
  2. 重复释放: 多次释放同一块内存,可能导致程序崩溃或不可预测的行为。

    // 错误示例
    int *arr = (int *)malloc(5 * sizeof(int));
    free(arr);
    free(arr);  // 重复释放相同的内存
    
  3. 使用已释放的内存: 在释放内存后,仍然尝试访问或修改已释放的内存。

    // 错误示例
    int *arr = (int *)malloc(5 * sizeof(int));
    free(arr);
    arr[0] = 10;  // 尝试访问已释放的内存
    
  4. 内存越界: 访问超出分配内存范围的位置,可能导致数据损坏或程序崩溃。

    // 错误示例
    int *arr = (int *)malloc(5 * sizeof(int));
    arr[5] = 10;  // 越界访问
    
  5. 使用未初始化的内存: 分配内存后,没有正确初始化就开始使用,导致未定义的行为。

    // 错误示例
    int *arr = (int *)malloc(5 * sizeof(int));
    int sum = 0;
    for (int i = 0; i < 5; i++) {sum += arr[i];  // 未初始化的内存访问
    }
    

这些错误在程序中可能导致严重的运行时问题,因此在动态内存管理时,务必注意正确地分配、使用和释放内存。使用 valgrind 等工具可以帮助检测和修复这些问题。

柔性数组

柔性数组(Flexible Array Member)是C语言中结构体的一种特殊用法,它允许在结构体的末尾定义一个数组,该数组的大小在运行时确定。柔性数组的声明通常如下所示:

struct ExampleStruct {// 其他成员int someMember;// 柔性数组成员float flexibleArray[];
};

在上面的示例中,flexibleArray 就是柔性数组成员。这个数组没有指定大小,而是留空。在使用柔性数组时,你可以根据需要动态分配内存。

请注意以下几点:

  1. 柔性数组只能出现在结构体的最后一个成员位置。
  2. 结构体中至少要有一个非柔性数组的成员。
  3. 柔性数组的大小不能在结构体内部指定,而是在运行时根据实际需要分配。

使用柔性数组时,通常需要使用动态内存分配函数(如malloc)为柔性数组成员分配内存。示例代码如下:

#include <stdio.h>
#include <stdlib.h>struct ExampleStruct {int someMember;float flexibleArray[];
};int main() {// 计算结构体总大小,包括柔性数组size_t structSize = sizeof(struct ExampleStruct) + 5 * sizeof(float);// 分配内存struct ExampleStruct *example = (struct ExampleStruct *)malloc(structSize);// 使用柔性数组for (int i = 0; i < 5; ++i) {example->flexibleArray[i] = i * 1.5;}// 释放内存free(example);return 0;
}

上述代码演示了柔性数组的声明和使用。在实际应用中,需要根据具体需求选择是否使用柔性数组,并谨慎处理内存分配和释放的问题。

总结

动态内存管理是C语言中一个强大而灵活的特性,通过它,我们可以在程序运行时动态分配和释放内存,更好地满足不同场景下的内存需求。在使用动态内存时,必须注意良好的管理原则,及时释放不再使用的内存,以防止内存泄漏和程序性能问题。总体而言,动态内存管理为C语言程序员提供了更大的灵活性和控制力。

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

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

相关文章

axios配置请求头content-type 和 get/post请求方式

axios配置请求头content-type https://blog.csdn.net/wojiushiwo945you/article/details/107653962 axios 是Ajax的一个插件&#xff0c;axios虽然是一个插件&#xff0c;但是我们不需要通过Vue.use(axios)来使用&#xff0c;下载完成后&#xff0c;只需在项目中引入即可。(一…

PHP文件上传以及数据写入

文件打开和数据写入 在PHP中&#xff0c;可以通过使用fopen()函数来打开一个文件。它接受两个参数&#xff1a;文件路径和打开模式。打开模式可以是"r"&#xff08;只读&#xff09;, "w"&#xff08;写入&#xff0c;如果文件不存在则创建文件&#xff0…

NFC刷卡soc芯片SI3262集成刷卡+触摸+ACD超低功耗一体

简介 13.56mhz刷卡soc芯片SI3262集成刷卡触摸ACD超低功耗&#xff0c;ACD模式刷卡距离可达到5cm以上&#xff0c;非常适用于小体积门锁&#xff0c;密码锁&#xff0c;柜锁&#xff0c;接下来介绍一下这款芯片的具体功能。 优势 1.超低功耗&#xff0c;最低功耗达 1.7uA&…

揭秘跨境电商ERP源码定制化需求及最佳实践

跨境电商ERP源码的定制化需求是跨境电商企业在整个ERP系统开发实施过程中需要重点关注的问题之一。本文将围绕跨境电商ERP源码定制化的需求和最佳实践展开深入探讨&#xff0c;为行业内的从业者和相关人士提供一些建议和思路。 定制化需求 跨境电商ERP的业务特点决定了对源码…

RivaGAN 水印项目

git地址 https://github.com/DAI-Lab/RivaGAN Dockerfile (/tools下文件为git下的文件) ############################################### # 使用 NVIDIA CUDA 10.0 开发环境作为基础镜像 FROM kaldiasr/kaldi:gpu-ubuntu18.04-cuda10.0 # 设置非交互式安装模式以避免某些命…

Vue 模板编译原理

Vue 模板编译原理是指将 Vue 的模板转换为渲染函数的过程。在 Vue 中&#xff0c;模板被定义为 HTML 代码片段或者在 .vue 单文件组件中定义。当 Vue 实例化时&#xff0c;会将模板编译为渲染函数&#xff0c;该函数可以根据组件的状态生成虚拟 DOM 并更新视图。 Vue 的模板编…

8.小明和完美序列

题目 import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int n sc.nextInt();sc.nextLine();Map<Integer,Intege…

Spring HTTP请求与应答国密算法加解密(对称加密方式SM4)

SM4算法成为行业标准: SM4分组密码算法是2012年3月21日实施的一项行业标准;2021年6月25日,我国SM4分组密码算法作为国际标准ISO/IEC 18033-3:2010/AMD1:2021《信息技术 安全技术 加密算法 第3部分:分组密码 补篇1:SM4》,由国际标准化组织ISO/IEC正式发布;中文名SM4分组…

Flask登陆后登陆状态及密码的修改和处理

web/templates/common 是统一布局 登录成功 后flask框架服务器默认由login.html进入仪表盘页面index.html(/),该页面的设置在 (web/controllers/user/index.py)&#xff0c;如果想在 该仪表盘页面 将 用户信息 展示出来&#xff0c;就得想办法先获取到 当前用户的 登陆状态。…

2022年全国职业院校技能大赛高职组云计算正式赛卷第三场-公有云

2022 年全国职业院校技能大赛高职组云计算赛项试卷 【赛程名称】云计算赛项第三场-公有云 目录 2022 年全国职业院校技能大赛高职组云计算赛项试卷 【赛程名称】云计算赛项第三场-公有云 【任务 1】公有云服务搭建[10 分] 【任务 2】公有云服务运维[10 分] 【任务 3】公有云运维…

兔子的序列

题目&#xff1a; 输入描述&#xff1a; 第一行一个整数 n&#xff0c;表示序列的长度。 第二行有 n 个整数 ai&#xff0c;表示序列中的 n 个数分别是多少。 输出描述&#xff1a; 输出仅一行&#xff0c;表示这个序列的名字&#xff0c;也就是这个序列中最大的非完全平方…

03.MySQL的体系架构

MySQL的体系架构 一、MySQL简介二、MySQL的体系架构三、MySQL的内存结构四、MySQL的文件结构 一、MySQL简介 MySQL是一个开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;由瑞典MySQL AB公司开发&#xff0c;后被Sun公司收购&#xff0c;Sun公司被Oracle…

【Web2D/3D】Canvas(第三篇)

1. 前言 <canvas>是HTML5新增元素&#xff0c;它是一个画板&#xff0c;开发人员基于它的2D上下文或webgl上下文&#xff0c;使用JS脚本绘制简单的动画、可交互画面&#xff0c;甚至进行视频渲染。 本篇介绍基于canvas的2D上下文绘制2D画面的一些方法和属性。 2. canvas…

【leetcode225】用队列实现栈Java代码讲解

12.27 225. 用队列实现栈 请你仅使用两个队列实现一个后入先出&#xff08;LIFO&#xff09;的栈&#xff0c;并支持普通栈的全部四种操作&#xff08;push、top、pop 和 empty&#xff09;。 实现 MyStack 类&#xff1a; void push(int x) 将元素 x 压入栈顶。int pop() 移除…

文件操作安全之-目录穿越流量告警运营分析篇

本文从目录穿越的定义,目录穿越的多种编码流量数据包示例,目录穿越的suricata规则,目录穿越的告警分析研判,目录穿越的处置建议等几个方面阐述如何通过IDS/NDR,态势感知等流量平台的目录穿越类型的告警的线索,开展日常安全运营工作,从而挖掘有意义的安全事件。 目录穿越…

向ES索引里面添加一个字段并更新旧文档数据

问题 最近需要调整ES索引&#xff0c;添加1个字段&#xff0c;并且&#xff0c;对旧文档数据更新新加的字段默认值。 解决思路 通过利用Update mapping API添加1个新字段后&#xff0c;然后&#xff0c;利用Update By Query API将向旧文档数据添加新加字段默认值。 添加字段…

鸿蒙Harmony(八)ArkUI--状态管理器之@State

状态管理 在声明式UI中&#xff0c;是以状态驱动视图更新 状态&#xff1a;指驱动视图更新的数据&#xff08;被装饰器标记的变量&#xff09; StateProp 和 LinkProvide和 Consume State State装饰器标记的变量必须初始化&#xff0c;不能为空值State支持Object 、class、…

ts中的任意接口

interface Persontest {name:string;[key:string]:any } var psss:Persontest{name:1,age:10,sex:男,[Symbol()]:灵境胡同 }ts中&#xff0c;[Symbol()] 是一个计算属性名称的语法&#xff0c;可以在对象字面量中使用 Symbol 类型来动态地生成属性名称&#xff0c;[Symbol()] 生…

【PostgreSQL内核学习(二十)—— 数据库中的遗传算法】

数据库中的遗传算法 概述个体的编码方式及种群初始化geqo 函数 适应值geqo_eval 函数gimme_tree 函数 父体选择策略geqo_selection 函数 杂交算子边重组杂交 ERX ( edge recombination crossover)gimme_edge_table 函数gimme_tour 函数 变异算子geqo_mutation 函数 声明&#x…

Java EE 网络原理之HTTP 响应详解

文章目录 1. 认识"状态码"(status code)2. 通过 form 表单构造 HTTP 请求3. 通过 ajax 构造 HTTP 请求 1. 认识"状态码"(status code) 表示了这次请求对应的响应&#xff0c;是什么样的状态 &#xff08;成功&#xff0c;失败&#xff0c;其他的情况&…