C 语言奇幻之旅 - 第11篇:C 语言动态内存管理
目录
- 引言
- 1. 内存分配函数
- 1.1 `malloc` 函数
- 实际开发场景:动态数组
- 1.2 `calloc` 函数
- 实际开发场景:初始化数据结构
- 1.3 `realloc` 函数
- 实际开发场景:动态调整数据结构大小
- 2. 内存释放
- 2.1 `free` 函数
- 3. 内存泄漏与调试
- 3.1 常见内存问题
- 3.2 内存调试工具
- 3.3 示例代码:内存泄漏检测
- 4. 高级话题:内存池与自定义内存分配器
- 4.1 内存池
- 5. 新增开发场景:链表的内存管理
- 实际开发场景:动态数据结构
- 结语
引言
欢迎来到 C 语言奇幻之旅的第 11 篇!今天我们将深入探讨 C 语言中的动态内存管理。在 C 语言的奇幻世界中,内存管理是一个既神秘又强大的领域。掌握动态内存管理,就像获得了一把打开无限可能的钥匙。本文将带你深入探索 C 语言中的动态内存管理,从基础的内存分配函数到高级的内存调试技巧,让你在编程的旅途中游刃有余。
1. 内存分配函数
在 C 语言中,动态内存管理主要通过以下几个函数来实现:malloc
、calloc
和 realloc
。这些函数允许程序在运行时动态地分配和调整内存,为复杂的数据结构和算法提供了强大的支持。
1.1 malloc
函数
malloc
是 C 语言中最常用的内存分配函数。它的原型如下:
void* malloc(size_t size);
size
:需要分配的内存大小,以字节为单位。- 返回值:指向分配内存的指针,如果分配失败则返回
NULL
。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {int *arr;int n = 5;// 分配内存arr = (int*)malloc(n * sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 初始化数组for (int i = 0; i < n; i++) {arr[i] = i + 1;}// 打印数组for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}
以下是使用更美观和优雅的方式展示的内存结构:
+-----+-----+-----+-----+-----+
| 1 | 2 | 3 | 4 | 5 |
+-----+-----+-----+-----+-----+▲ ▲ ▲ ▲ ▲│ │ │ │ │arr[0] arr[1] arr[2] arr[3] arr[4]
实际开发场景:动态数组
在实际开发中,malloc
常用于动态数组的创建。例如,当数组大小在运行时才能确定时,可以使用 malloc
动态分配内存。
1.2 calloc
函数
calloc
函数与 malloc
类似,但它会将分配的内存初始化为零。它的原型如下:
void* calloc(size_t num, size_t size);
num
:需要分配的元素个数。size
:每个元素的大小,以字节为单位。- 返回值:指向分配内存的指针,如果分配失败则返回
NULL
。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {int *arr;int n = 5;// 分配并初始化内存arr = (int*)calloc(n, sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 打印数组for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}
以下是使用更美观和优雅的方式展示的内存结构:
+-----+-----+-----+-----+-----+
| 0 | 0 | 0 | 0 | 0 |
+-----+-----+-----+-----+-----+▲ ▲ ▲ ▲ ▲│ │ │ │ │arr[0] arr[1] arr[2] arr[3] arr[4]
实际开发场景:初始化数据结构
calloc
常用于需要初始化内存的场景,例如创建并初始化一个结构体数组。
1.3 realloc
函数
realloc
函数用于调整已分配内存的大小。它的原型如下:
void* realloc(void* ptr, size_t size);
ptr
:指向之前分配的内存块的指针。size
:新的内存大小,以字节为单位。- 返回值:指向新分配内存的指针,如果分配失败则返回
NULL
。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {int *arr;int n = 5;// 分配内存arr = (int*)malloc(n * sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 初始化数组for (int i = 0; i < n; i++) {arr[i] = i + 1;}// 调整内存大小n = 10;arr = (int*)realloc(arr, n * sizeof(int));if (arr == NULL) {printf("内存重新分配失败\n");return 1;}// 初始化新增的元素for (int i = 5; i < n; i++) {arr[i] = i + 1;}// 打印数组for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}
以下是使用更美观和优雅的方式展示的内存结构:
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲ ▲│ │ │ │ │ │ │ │ │ │arr[0] arr[1] arr[2] arr[3] arr[4] arr[5] arr[6] arr[7] arr[8] arr[9]
实际开发场景:动态调整数据结构大小
realloc
常用于需要动态调整数据结构大小的场景,例如动态数组的扩容。
2. 内存释放
在动态内存管理中,分配的内存必须在使用完毕后释放,以避免内存泄漏。C 语言提供了 free
函数来完成这一任务。
2.1 free
函数
free
函数的原型如下:
void free(void* ptr);
ptr
:指向之前分配的内存块的指针。
示例代码
#include <stdio.h>
#include <stdlib.h>int main() {int *arr;int n = 5;// 分配内存arr = (int*)malloc(n * sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 初始化数组for (int i = 0; i < n; i++) {arr[i] = i + 1;}// 打印数组for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存free(arr);return 0;
}
以下是使用更美观和优雅的方式展示的内存结构:
+-----+-----+-----+-----+-----+
| 1 | 2 | 3 | 4 | 5 |
+-----+-----+-----+-----+-----+▲ ▲ ▲ ▲ ▲│ │ │ │ │arr[0] arr[1] arr[2] arr[3] arr[4]
释放后,内存块被标记为可用,可以被重新分配。
3. 内存泄漏与调试
内存泄漏是动态内存管理中的常见问题,它会导致程序占用的内存不断增加,最终可能导致系统崩溃。了解如何检测和修复内存泄漏是每个 C 语言开发者的必备技能。
3.1 常见内存问题
问题类型 | 描述 | 解决方法 |
---|---|---|
内存泄漏 | 分配的内存未被释放,导致内存占用不断增加。 | 使用 free 函数释放内存,确保每次分配都有对应的释放。 |
野指针 | 指向已释放内存的指针,访问这些指针会导致未定义行为。 | 在释放内存后将指针设置为 NULL 。 |
双重释放 | 同一块内存被释放多次,导致未定义行为。 | 确保每块内存只被释放一次。 |
内存越界 | 访问超出分配内存范围的数据,导致未定义行为。 | 确保访问的内存范围在分配的内存范围内。 |
3.2 内存调试工具
工具名称 | 描述 | 使用方法 |
---|---|---|
Valgrind | 一个强大的内存调试工具,可以检测内存泄漏、野指针等问题。 | 使用 valgrind --leak-check=full ./your_program 运行程序。 |
AddressSanitizer | 一个内存错误检测工具,可以检测内存越界、使用已释放内存等问题。 | 编译时添加 -fsanitize=address 选项。 |
GDB | GNU 调试器,可以用于调试内存相关问题。 | 使用 gdb ./your_program 启动调试会话。 |
3.3 示例代码:内存泄漏检测
#include <stdio.h>
#include <stdlib.h>void memory_leak_example() {int *arr = (int*)malloc(5 * sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return;}// 忘记释放内存
}int main() {memory_leak_example();return 0;
}
使用 Valgrind 检测内存泄漏:
valgrind --leak-check=full ./memory_leak_example
输出结果:
==12345== HEAP SUMMARY:
==12345== in use at exit: 20 bytes in 1 blocks
==12345== total heap usage: 1 allocs, 0 frees, 20 bytes allocated
==12345==
==12345== 20 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==12345== by 0x4005E6: memory_leak_example (memory_leak_example.c:6)
==12345== by 0x400606: main (memory_leak_example.c:14)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 20 bytes in 1 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 0 bytes in 0 blocks
==12345== still reachable: 0 bytes in 0 blocks
==12345== suppressed: 0 bytes in 0 blocks
==12345==
==12345== For counts of detected and suppressed errors, rerun with: -v
==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
4. 高级话题:内存池与自定义内存分配器
在实际开发中,频繁调用 malloc
和 free
可能会导致性能问题。为了解决这个问题,可以使用内存池或自定义内存分配器。
4.1 内存池
内存池是一种预先分配一大块内存,然后在程序运行期间从这块内存中分配和释放内存的技术。它可以减少内存碎片和提高内存分配效率。
示例代码
#include <stdio.h>
#include <stdlib.h>#define POOL_SIZE 1024char memory_pool[POOL_SIZE];
size_t pool_index = 0;void* pool_alloc(size_t size) {if (pool_index + size > POOL_SIZE) {return NULL; // 内存池不足}void* ptr = &memory_pool[pool_index];pool_index += size;return ptr;
}void pool_free() {pool_index = 0; // 重置内存池
}int main() {int *arr = (int*)pool_alloc(5 * sizeof(int));if (arr == NULL) {printf("内存池不足\n");return 1;}// 使用内存for (int i = 0; i < 5; i++) {arr[i] = i + 1;}// 打印数组for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}printf("\n");// 释放内存池pool_free();return 0;
}
5. 新增开发场景:链表的内存管理
在实际开发中,链表是一种常见的数据结构,它需要动态分配和释放内存。以下是一个简单的单向链表示例,展示了如何使用 malloc
和 free
来管理链表节点的内存。
示例代码
#include <stdio.h>
#include <stdlib.h>// 定义链表节点结构
typedef struct Node {int data;struct Node* next;
} Node;// 创建新节点
Node* create_node(int data) {Node* new_node = (Node*)malloc(sizeof(Node));if (new_node == NULL) {printf("内存分配失败\n");return NULL;}new_node->data = data;new_node->next = NULL;return new_node;
}// 释放链表
void free_list(Node* head) {Node* current = head;Node* next;while (current != NULL) {next = current->next;free(current);current = next;}
}// 打印链表
void print_list(Node* head) {Node* current = head;while (current != NULL) {printf("%d -> ", current->data);current = current->next;}printf("NULL\n");
}int main() {// 创建链表Node* head = create_node(1);head->next = create_node(2);head->next->next = create_node(3);// 打印链表print_list(head);// 释放链表free_list(head);return 0;
}
以下是链表的内存结构:
+-----+-----+ +-----+-----+ +-----+-----+
| 1 | ────> | 2 | ────> | 3 | NULL |
+-----+-----+ +-----+-----+ +-----+-----+▲ ▲ ▲│ │ │head head->next head->next->next
实际开发场景:动态数据结构
链表是一种典型的动态数据结构,它需要在运行时动态分配和释放内存。通过 malloc
和 free
,我们可以灵活地管理链表节点的内存。
结语
通过本文的学习,你已经掌握了 C 语言中动态内存管理的基本概念和技巧。从 malloc
、calloc
到 realloc
,再到 free
函数的使用,你已经具备了在复杂程序中管理内存的能力。同时,你也了解了如何检测和修复内存泄漏等常见问题。
记住,掌握内存管理是成为一名优秀开发者的关键一步。继续探索,继续学习,你将在 C 语言的奇幻世界中发现更多的宝藏!
希望这篇博客能为你提供有价值的信息,并激发你对 C 语言学习的兴趣。如果有任何问题或建议,欢迎随时告诉我!😊
相关文章:
C 语言奇幻之旅 - 第11篇:C 语言动态内存管理
目录 引言1. 内存分配函数1.1 malloc 函数实际开发场景:动态数组 1.2 calloc 函数实际开发场景:初始化数据结构 1.3 realloc 函数实际开发场景:动态调整数据结构大小 2. 内存释放2.1 free 函数 3. 内存泄漏与调试3.1 常见内存问题3.2 内存调试…...

IDEA 撤销 merge 操作(详解)
作为一个开发者,我们都知道Git是一个非常重要的版本控制工具,尤其是在协作开发的过程中。然而,在使用Git的过程中难免会踩一些坑,今天我来给大家分享一个我曾经遇到的问题:在使用IDEA中进行merge操作后如何撤销错误的合…...
swarm天气智能体调用流程
Swarm 框架的调用流程: 入口点 (examples/weather_agent/run.py): run_demo_loop(weather_agent, streamTrue)初始化流程: # swarm/repl/repl.py -> run_demo_loop() client Swarm() # 创建 Swarm 实例消息处理流程: # swarm/core.py class Swarm:def run(…...

LED背光驱动芯片RT9293应用电路
一)简介: RT9293 是一款高频、异步的 Boost 升压型 LED 定电流驱动控制器,其工作原理如下: 1)基本电路结构及原理 RT9293的主要功能为上图的Q1. Boost 电路核心原理:基于电感和电容的特性实现升压功能。当…...
二叉树的二叉链表和三叉链表
在二叉树的数据结构中,通常有两种链表存储方式:二叉链表和三叉链表。这里,我们先澄清一下概念,通常我们讨论的是二叉链表,它用于存储二叉树的节点。而“三叉链表”这个术语在二叉树的上下文中不常见,可能是…...

【学习路线】Python 算法(人工智能)详细知识点学习路径(附学习资源)
学习本路线内容之前,请先学习Python的基础知识 其他路线: Python基础 >> Python进阶 >> Python爬虫 >> Python数据分析(数据科学) >> Python 算法(人工智能) >> Pyth…...
C++直接内存管理new和delete
0、前言 C语言定义了两个运算符来分配和释放动态内存。运算符new分配内存,delete释放new分配的内存。 1、new动态内存的分配 1.1、new动态分配和初始化对象 1)、new内存分配 在自由的空间分配的内存是无名的,new无法为其分配的对象…...
Linux 内核中网络接口的创建与管理
在 Linux 系统中,网络接口(如 eth0、wlan0 等)是计算机与外部网络通信的桥梁。无论是物理网卡还是虚拟网络接口,它们的创建和管理都依赖于 Linux 内核的复杂机制。本文将深入探讨 Linux 内核中网络接口的创建过程、命名规则、路由选择以及内核如何将网络接口映射到实际的硬…...
人工智能 前馈神经网络练习题
为了构建一个有两个输入( X 1 X_1 X1、 X 2 X_2 X2)和一个输出的单层感知器,并进行分类,我们需要计算权值 w 1 w_1 w1和 w 2 w_2 w2的更新过程。以下是详细的步骤和计算过程: 初始化参数 初始权值:…...

Windows搭建RTMP服务器
目录 一、Nginx-RTMP服务器搭建1、下载Nginx2、下载Nginx的RTMP扩展包3、修改配置文件4、启动服务器5、查看服务器状态6、其它ngnix命令 二、OBS推流1 、推流设置2、查看服务器状态 三、VLC拉流四、补充 本文转载自:Windows搭建RTMP服务器OBS推流VLC拉流_浏览器查看…...
Vue重新加载子组件
背景:组件需要重新加载,即重新走一遍组件的生命周期常见解决方案: 使用v-if指令:v-if 可以实现 true (加载)和 false (卸载) async reloadComponent() {this.show false// 加上 nextTick this.$nextTick(function() {this.show…...

【VScode】设置代理,通过代理连接服务器
文章目录 VScode编辑器设置代理1.图形化界面1.1 进入proxy设置界面1.2 配置代理服务器 2.配置文件(推荐)2.1 打开setting.json 文件2.2 配置代理 VScode编辑器设置代理 根据情况安装nmap 1.图形化界面 1.1 进入proxy设置界面 或者使用快捷键ctrl , 。…...
js es6 reduce函数, 通过规格生成sku
const specs [{ name: 颜色, values: [红色, 蓝色, 绿色] },{ name: 尺寸, values: [S, M, L] } ];function generateSKUs(specs) {return specs.reduce((acc, spec) > {const newAcc [];for (const combination of acc) {for (const value of spec.values) {newAcc.push(…...
基于R语言的DICE模型
DICE型是运用最广泛的综合模型之一。DICE和RICE模型虽然代码量不多,但涉及经济学与气候变化,原理较为复杂。 一:DICE模型的原理与推导 1.经济学 2.气候变化问题 3.DICE模型的经济学部分 4.DICE模型的气候相关部分 5.DICE模型的目标函数…...
【C】PAT 1006-1010
1006 换个格式输出整数 让我们用字母 B 来表示“百”、字母 S 表示“十”,用 12...n 来表示不为零的个位数字 n(<10),换个格式来输出任一个不超过 3 位的正整数。例如 234 应该被输出为 BBSSS1234,因为它有 2 个“…...

力扣双指针-算法模版总结
lc-15.三数之和 (时隔13天) 目前可通过,想法上无逻辑问题,一点细节小错误需注意即可 lc-283.移动零(时隔16天) 总结:观察案例直觉就是双指针遇零交换,两次实现都通过了,…...

解释一下:运放的输入偏置电流
输入偏置电流 首先看基础部分:这就是同相比例放大器 按照理论计算,输入VIN=0时,输出VOUT应为0,对吧 仿真与理论差距较大,有200多毫伏的偏差,这就是输入偏置电流IBIAS引起的,接着看它的定义 同向和反向输入电流的平均值,也就是Ib1、Ib2求平均,即(Ib1+Ib2)/2 按照下面…...
Windows 11 上通过 WSL (Windows Subsystem for Linux) 安装 MySQL 8
在 Windows 11 上通过 WSL (Windows Subsystem for Linux) 安装 MySQL 8 的步骤如下: ✅ 1. 检查 WSL 的安装 首先确保已经安装并启用了 WSL 2。 🔧 检查 WSL 版本 打开 PowerShell,执行以下命令: wsl --list --verbose确保 W…...

信用租赁系统助力企业实现免押金租赁新模式
内容概要 在现代商业环境中,信用租赁正在迅速崛起。通过结合大数据与区块链技术,信用租赁系统彻底改变了传统的租赁流程。什么是信用租赁呢?简单说,就是不需要押金,你也能够租到你想要的物品,这对企业和消…...

OSPF特殊区域(open shortest path first LSA Type7)
一、区域介绍 1、Stub区域 Stub区域是一种可选的配置属性。通常来说,Stub区域位于自治系统的边界,例如,只有一 个ABR的非骨干区域。在这些区域中,设备的路由表规模以及路由信息传递的数量都会大量减少。 kill 4 5类type 传递1 …...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级
在互联网的快速发展中,高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司,近期做出了一个重大技术决策:弃用长期使用的 Nginx,转而采用其内部开发…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...

MyBatis中关于缓存的理解
MyBatis缓存 MyBatis系统当中默认定义两级缓存:一级缓存、二级缓存 默认情况下,只有一级缓存开启(sqlSession级别的缓存)二级缓存需要手动开启配置,需要局域namespace级别的缓存 一级缓存(本地缓存&#…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...