C语言——内存管理
目录
前言
一、内存分类
1. 栈区(Stack)
2. 堆区(Heap)
3. 数据段(Data Segment)
4. 代码段(Code Segment)
二、内存分配方式
1、静态内存分配
2、栈内分配
3、动态内存分配
(1)如何使用malloc 函数:
(2)内存释放
(3)内存泄漏
三、常见内存错误——野指针
1、未初始化的指针
2、 指针释放后未置空
3、局部变量指针逃逸
4、指针运算错误
完结
前言
C语言中,内存管理需要对静态和动态内存分配,静态分配在编译时确定,而动态分配(如malloc, calloc, realloc)则在运行时进行,需程手动管理,包括适时释放(用free)以避免内存泄漏。同时,了解栈、堆、数据区、代码区等内存区域的特性和用途,对于有效管理内存至关重要。
一、内存分类
可将内存简单分为:栈区、堆区、静态区,其中静态区包含数据段、代码段的内容,主要存储常量、字符串常量等只读数据、已初始化的全局变量和静态变量以及未初始化的全局变量和静态变量。
堆栈主要指的是栈,而不是堆。
1. 栈区(Stack)
定义:栈区用于存储函数的局部变量、函数参数和返回地址。栈区内存由系统自动分配和释放,具有后进先出(LIFO)的特性。
特点:1、分配效率高,但空间有限;
2、编译器自动管理,无需程序员手动释放。
用于保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容 也会自动被销毁。
2. 堆区(Heap)
定义:堆区用于动态分配内存,即程序运行时使用malloc、new等函数分配的内存。
特点:1、可以动态分配和释放大块内存。
2、内存由程序员手动管理,需要手动释放(使用free函数)。
3、内存分配效率相对较低,但空间较大。
其生命周期由 free 或 delete 决定。 在没有释放之前一直存在,直到程序结束。
3. 数据段(Data Segment)
数据段又可以进一步细分为:
1、已初始化数据段:存储已初始化的全局变量和静态变量(static)。这些变量在程序加载时分配内存,并在程序结束时释放。
2、未初始化数据段(BSS段):存储未初始化的全局变量和静态变量。BSS段在程序启动时自动初始化为零或空指针,但它在程序加载时并不占用磁盘空间(因为其内容默认为零)。
4. 代码段(Code Segment)
定义:代码段也称为文本段,存储程序的机器指令。
特点:1、通常是只读的,以防止程序意外修改指令。
2、包含常量字符串等只读数据。
函数定义和字符串常量存储在代码段。
二、内存分配方式
1、静态内存分配
静态内存分配是在程序编译时进行的,它将内存分配给全局变量和静态变量,存在周期最长。
优点:内存分配和释放的效率高。
缺点:内存使用不灵活,无法根据需要动态调整内存大小。
char UART_RxBUF[100] = {0};//已初始化全局变量
char flag;//未初始化全局变量static int count;//静态全局变量,只能在本文件内使用void fun()
{static int i;//静态变量//fun主体
}
2、栈内分配
栈内存分配是在程序运行时进行的,它将内存分配给函数内部的局部变量。
优点:内存管理简单,不需要程序员手动释放。
缺点:内存空间有限,不适合分配大内存,且存在栈溢出的风险。
void fun()
{int i = 0;char buf[100] = {0};//函数运行结束,就会释放
//fun主体
}
3、动态内存分配
动态内存分配是在程序运行时根据需要进行的内存分配。
优点:内存使用灵活,可以根据需要动态调整内存大小。
缺点:内存管理复杂,需要程序员手动分配和释放,容易出现内存泄漏等问题。
常用的动态内存分配函数包括malloc()、calloc()和realloc(),分别用于分配内存、分配并初始化为0的内存、以及重新调整已分配内存的大小。
(1)如何使用malloc 函数:
其原型为:
(void *)malloc(int size)
malloc 函数的返回值是一个 void 类型的指针,参数为 int 类型数据,即申请分配的内存 大小,单位是 byte。内存分配成功之后,malloc 函数返回这块内存的首地址。你需要一个指 针来接收这个地址。但是由于函数的返回值是 void *类型的,所以必须强制转换成你所接收 的类型。也就是说,这块内存将要用来存储什么类型的数据。比如:
int *ptr = (int*)malloc(10 * sizeof(int));
//分配了10个整数(int)的内存空间
在堆上分配了 10 个int类型的内存(若在编译器中int占四个字节,那么分配内大小为40个字节),返回这块内存的首地址,把地址强制转换成 int *类型后赋给 int *类型的指针变量 ptr。同时告诉我们这块内存将用来存储 int类型的数据。也就是说只能通过指针变量 ptr 来操作这块内存。这块内存本身并没有名字,对它的访问是匿名访问。
上面就是使用 malloc 函数成功分配一块内存的过程。但是,每次你都能分配成功吗? 不一定。使用 malloc 函数同样要注意:如果所申请的内存块大于目前堆上剩余内存块,则内存分配会失败,函数返回 NULL。
注意:这里说的“堆上剩余内存块”不是所有剩余内存块之和,因 为 malloc 函数申请的是连续的一块内存,所以只要堆上剩余的、连续的内存块没有能满足所申请的内存大小就会申请失败。
既然 malloc 函数申请内存有不成功的可能,那我们在使用指向这块内存的指针时,必须用 if(NULL != p)语句来验证内存确实分配成功了。
int *ptr = (int*)malloc(10 * sizeof(int));
//分配了10个整数(int)的内存空间if(ptr != NULL)
{printf("内存申请成功!\n");
}
如果用 malloc 函数申请 0 字节内存时,函数并 不返回 NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为 0 的内存。这时候 if(NULL != p)语句校验将不起作用。
(2)内存释放
既然有分配,那就必须有释放。在C语言中,使用动态内存分配后,程序员需要负责在适当的时候释放这些内存,不然的话,有限的内存总会用光,而没有释放的内存却在空闲,导致内存泄漏。与 malloc 对应的就是 free 函数了。free 函数只有一个参数,就是所要释放的内存块的首地址,该函数只能用于释放通过malloc()、calloc()或realloc()等函数分配的内存。比如上例:
int *ptr = (int*)malloc(10 * sizeof(int));
if(ptr != NULL)
{printf("内存申请成功!\n");
}free(ptr);ptr = NULL;
切记,申请次数和释放次数要对应。malloc 两次只 free 一次会内存泄漏;malloc 一次 free 两次肯定会出错。也就是说,在程序 中 malloc 的使用次数一定要和 free 相等,否则必有错误。虽然使用 free 函数释放了内存,但指针变量 p 本身保存的地址并没有改变,就需要重新把 p 的值变为 NULL:否则这个指针就成为了“野指针”,也有书叫“悬 垂指针”。这是很危险的,而且也是经常出错的地方。所以一定要记住一条:free 完之后, 一定要给指针置 NULL。
(3)内存泄漏
内存泄漏(Memory Leak)是指程序中已分配的内存由于某种原因未被释放或无法释放,导致该内存块持续被占用,无法再被程序或系统用于其他目的。随着内存泄漏的不断积累,可用内存逐渐减少,最终可能导致程序运行速度变慢、响应迟缓,甚至崩溃。在程序中动态分配的内存(如使用 malloc、new 等函数或操作符)在不再需要时,应当通过相应的释放函数(如 free、delete)进行释放。如果忘记释放,就会导致内存泄漏。
三、常见内存错误——野指针
野指针是一个指针变量,所指向的地址是未知的、随机的、不正确的或没有明确限制的,以及那些已经被释放的内存地址。这种指针在尝试访问或修改其所指向的内存时,会导致不可预测的行为,甚至程序崩溃。野指针的存在是编程中的一个严重问题,因为它可能导致程序崩溃、数据损坏或其他不可预期的行为。在严重的情况下,野指针还可能被恶意利用,造成安全漏洞。
1、未初始化的指针
在C或C++中,声明一个指针变量时,如果没有立即为其分配内存或初始化为NULL,它将包含一个随机的地址。尝试访问这个随机地址会导致不可预知的后果。如:
#include <stdio.h>struct student
{
char *name;
int score;
}stu;int main()
{
strcpy(stu.name,"Jimy");
return 0;
}
/*这里定义了结构体变量 stu,但是他没想到这个结构体内部 char *name
这成员在定义结构体变量 stu 时,只是给 name 这个指针变量本身分配了
4 个字节。name 指针并没有指向一个合法的地址,这时候其内部存的只是
一些乱码。所以在调用 strcpy 函数时,会将字符串"Jimy"往乱码所指的内
存上拷贝,而这块内存 name 指针根本就无权访问,导致出错。解决的办法
是为 name 指针 malloc 一块空间。*/
2、 指针释放后未置空
使用free()或delete等函数释放了指针指向的内存后,如果不将指针置为NULL,那么这个指针就变成了野指针。在后续的代码中,如果错误地尝试通过这个指针访问内存,将会导致不可预测的行为。
int *ptr = (int*)malloc(10 * sizeof(int));
if(ptr != NULL) printf("内存申请成功!\n");
free(ptr);// ptr = NULL;
*ptr = 100;
*(ptr+1) = 200;
//ptr已经被释放,错误操作
3、局部变量指针逃逸
当函数返回时,其栈上的局部变量将不再有效。如果指针仍然指向这些局部变量,它们将成为野指针。如:
#include <stdio.h> int* fun()
{ int value = 10; // 局部变量 return &value; // 返回局部变量的地址
} int main(){ int* ptr = fun(); // ptr 指向了 fun函数中的局部变量 value 的地址 printf("%d\n", *ptr); // 这里可能打印出 10,但这是未定义行为 // 当 fun函数执行完毕后,value 所占用的内存已经被释放 // 此时 ptr 是一个野指针,访问它会导致未定义行为 // 但在某些情况下(比如没有立即重用该内存区域),它可能看起来还在工作 return 0;
}
正确方式:
#include <stdio.h>
#include <stdlib.h>
// 函数声明,返回一个指向int的指针
int* createInteger(int value) { // 在堆上分配内存来存储一个int值 int* ptr = (int*)malloc(sizeof(int)); if (ptr == NULL) { // 如果malloc失败,返回NULL return NULL; } *ptr = value; // 初始化内存中的值 return ptr; // 返回指向新分配内存的指针
} // 注意:在C语言中,我们通常不编写专门的函数来释放内存,因为这通常是调用者的责任 int main() { int* myInteger = createInteger(42); // 调用函数,分配内存并初始化 if (myInteger != NULL) { // 检查指针是否为NULL,以避免解引用空指针 printf("The value is: %d\n", *myInteger); // 使用指针访问值 // ... 在这里可以使用myInteger做一些事情 ... // 释放之前分配的内存 free(myInteger); // 将指针置为NULL,避免成为野指针(这是一个好习惯) myInteger = NULL; } else { // 处理内存分配失败的情况 printf("Memory allocation failed!\n"); } return 0;
}
4、指针运算错误
对指针进行算术运算时,如果运算后的指针指向了未知或无效的内存区域,也会形成野指针。如:
#include <stdio.h> int main() { int arr[5] = {1, 2, 3, 4, 5}; // 定义一个有5个元素的数组 int *ptr = arr; // 指针ptr指向数组的第一个元素 // 正确访问数组元素 for (int i = 0; i < 5; i++) { printf("%d ", *(ptr + i)); // 输出1 2 3 4 5 } // 指针运算错误:尝试访问数组之外的内存 ptr += 5; // ptr现在指向arr[5],但实际上arr[5]不存在,这是越界 printf("%d\n", *ptr); // 尝试解引用ptr,这是未定义行为 // 在某些情况下,上面的代码可能不会立即崩溃,但会导致不可预测的结果 // 因为ptr现在指向了一个未定义的内存位置 return 0;
}
完结
有误之处望指正!!!
相关文章:
C语言——内存管理
目录 前言 一、内存分类 1. 栈区(Stack) 2. 堆区(Heap) 3. 数据段(Data Segment) 4. 代码段(Code Segment) 二、内存分配方式 1、静态内存分配 2、栈内分配 3、动态内存分配 &#x…...
Unity UGUI 之 Image和Rawimage
本文仅作学习笔记与交流,不作任何商业用途 本文包括但不限于unity官方手册,唐老狮,麦扣教程知识,引用会标记,如有不足还请斧正 1.Image是什么 Unity - 手册:图像 精灵格式是什么? 1.2重要参数 …...
Lua 语法学习笔记
Lua 语法学习笔记 安装(windows) 官网:https://www.lua.org/ 下载SDK 解压&修改名称(去除版本号) 将lua后面的版本号去掉,如lua54.exe->lua.ext 配置环境变量 数据类型 数据类型描述nil这个最简单,只有值n…...
Prometheus配置alertmanager告警
1、拉取镜像并运行 1、配置docker镜像源 [rootlocalhost ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://dfaad.mirror.aliyuncs.com"] } [rootlocalhost ~]# systemctl daemon-reload [rootlocalhost ~]# systemctl restart docker2、…...
.net core 外观者设计模式 实现,多种支付选择
1,接口 /// <summary>/// Web页面支付/// </summary>public interface IWebPagePay{public WebPagePayResult CreatePay(string productName, string orderSn, string totalPrice);}2,实现接口 实现阿里支付 public class AliPagePay : IWe…...
Matlab 命令行窗口默认输出(异常)
目录 前言Matlab 先验知识1 异常输出的代码2 正常输出的代码 前言 在单独调试 Matlab 写的函数时出现不想出现的异常打印值,逐个注释排查才找到是 if elseif else 代码块的问题,会默认打印输出 else 部分第一个返回值的值(下方代码中的 P值&…...
LeetCode/NowCoder-二叉树OJ练习
励志冰檗:形容在清苦的生活环境中激励自己的意志。💓💓💓 目录 说在前面 题目一:单值二叉树 题目二:相同的树 题目三:对称二叉树 题目四:二叉树的前序遍历 题目五:另…...
PSINS工具箱函数介绍——insplot
insplot是一个绘图命令,用于将avp数据绘制出来 本文所述的代码需要基于PSINS工具箱,工具箱的讲解: PSINS初学指导基于PSINS的相关程序设计(付费专题)使用方法 此函数使用起来也很简单,直接后面加avp即可,如: insplot(avp);其中,avp为: 每行表示一个时间1~3列为姿态…...
Docker简单快速入门
1. 安装Docker 基于 Ubuntu 24.04 LTS 安装Docker 。 # 更新包索引并安装依赖包 sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common# 添加Docker的官方GPG密钥并存储在正确的位置 curl -fsSL https://mirror…...
【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 图像物体的边界(200分) - 三语言AC题解(Python/Java/Cpp)
🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…...
【无人机】低空经济中5G RedCap芯片的技术分析报告
1. 引言 图一. 新基建:低空经济 低空经济作为一种新兴的经济形态,涵盖了无人机、电动垂直起降飞行器(eVTOL)、低空物流、空中交通管理等多个领域。随着5G网络的普及和演进,5G RedCap(Reduced Capability&a…...
MongoDB教程(二十一):MongoDB大文件存储GridFS
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 文章目录 引言一、GridFS…...
vue 搜索框
效果 创建搜索组件: 在Vue项目中,首先需要创建一个搜索组件。这个组件通常包含一个输入框和一个搜索按钮。使用v-model指令将输入框与组件的数据属性(如searchKeyword)进行双向绑定,以便获取用户输入的关键词。处理搜索…...
国科大作业考试资料-人工智能原理与算法-2024新编-第五次作业整理
1、本题以井字棋(圈与十字游戏)为例练习博弈中的基本概念。定义X_n为恰好有n个X而没有O 的行、列或者对角线的数目。同样O_n为正好有n 个O的行、列或者对角线的数目。效用函数给 X_3=1的棋局+1, 给O_3=1的棋局-1。所有其他终止状态效用值为0。对于非终止状态,使用线性的 …...
C++五子棋(未做完,但能玩,而且还不错)
代码放下面了,关于步骤介绍的我以后再完善一下。 #include<bits/stdc.h> #include<cstdio> #include<cstdlib> #include<ctime> #include<windows.h> #include<stdlib.h> #include<time.h> #define random(x) (rand()%x…...
二分查找代码详解
二分查找代码实现 以下是完整的代码和解释: #include <stdio.h>int binarySearch(int arr[], int length, int target) {int left 0;int right length - 1;while (left < right) {int mid left (right - left) / 2; // 防止溢出if (arr[mid] target…...
uniapp的h5,读取本地txt带标签的文件
效果图 使用的回显的标签是u-parse,下面的网址讲了这个标签的相关 https://www.cnblogs.com/huihuihero/p/12978903.html 导入此插件 https://ext.dcloud.net.cn/plugin?id364 使用 uni.request({// 本地文件url: "/static/互联网医院医师端用户协议.txt…...
韦东山嵌入式linux系列-具体单板的按键驱动程序(查询方式)
1 GPIO 操作回顾 (1)使能模块; (2)设置引脚的模式(工作于GPIO模式); (3)设置GPIO本身(输入/输出); (4&…...
如何使用 API list 极狐GitLab 群组中的镜像仓库?
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab :https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署…...
PHP设计模式-简单工厂模式
核心: 一、定义一个接口类里面写规定好的方法。 interface Message{public function send(array $params);public function getMessage(array $params);public function getCode(array $params);} 二、定义产品类 、产品类继承接口类 class AlliYunSms implements …...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...
