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 …...

C语言航空售票系统
以下是系统部分页面 以下是部分源码,需要源码的私信 #include<stdio.h> #include<stdlib.h> #include<string.h> #define max_user 100 typedef struct ft {char name[50];//名字char start_place[50];//出发地char end_place[50];//目的地char …...

Oracle 19c打Datapatch数据补丁报错处理
Oracle 19c打Datapatch数据补丁报错处理 错误分析重新编译补丁验证安装完数据库补丁后,在数据补丁的步骤收到以下报错: Connecting to database...OK Gathering database info...done Bootstrapping registry and package to current versions...done Determining current s…...

Linux shell编程学习笔记66:ping命令 超详细的选项说明
0 前言 网络信息是电脑网络信息安全检查中的一块重要内容,Linux和基于Linux的操作系统,提供了很多的网络命令,今天我们研究最常用的ping命令。 1 ping命令 的功能、格式和选项说明 1.1 ping命令 的功能 简单来说, ping 命令 会…...

SSL/TLS和SSL VPN
1、SSL/TLS SSL安全套接字层:是一种加密协议,用于在网络通信中建立安全连接。它在应用层和传输层(TCP/IP)之间提供数据加密、服务器身份验证以及信息完整性验证 SSL只保护TCP流量,不保护UDP协议 TLS:传输层…...

浅谈WebSerice
一. 什么是WebService Web Service也称为web服务,它是一种跨编程语言和操作系统平台的远程调用技术。Web Service采用标准的SOAP协议传输(SOAP:Simple Object Access Protocol简单对象访问协议,soap属于w3c标准。并且soap协议是基…...

linux快速入门-学习笔记
linux快速入门-学习笔记 第一章:Linux系统概念及命令学习Linux系统基本概念命令终端介绍命令格式介绍Linux系统辨别目录与文件的方法通过文件详细属性辨别ls 查看目录/文件命令Linux 系统下的归属关系命令行编辑技巧Linux 基本权限的类别课后练习 第二章:…...

科普文:5种Linux下软件部署方式说明
在Linux世界里,高效、灵活地安装和管理软件是每个系统管理员和开发者的基本功。从传统的RPM包管理,到便捷的YUM软件仓库,再到颠覆性的Docker容器技术,Snap,源码安装,每一种方法都有其独到之处,适…...

Redisson中的RBlockingQueue的使用场景及例子
Redisson 的 RBlockingQueue 是一个实现了 Java BlockingQueue 接口的分布式队列,它可以用于在分布式系统中实现生产者-消费者模式。RBlockingQueue 提供了线程安全的阻塞队列操作,允许生产者在队列满时阻塞,消费者在队列空时阻塞,…...

【办公软件】Office 2019以上版本PPT 做平滑切换
Office2019以上版本可以在切页面时做平滑切换,做到一些简单的动画效果。如下在快捷菜单栏中的切换里选择平滑。 比如,在两页PPT中,使用同一个形状对象,修改了大小和颜色。 选择切换为平滑后,可以完成如下的动画显示。 …...

connect-multiparty中间件用法以及实例--文件上传中间件(保姆级别教学)
connect-multiparty中间件的用法包括安装和引入、基本设置、路由应用、文件处理以及安全和优化等步骤。 connect-multiparty是一个专为Connect和Express框架设计的文件上传中间件,它基于multiparty库,用于处理多部分表单数据,尤其针对文件上传…...