当前位置: 首页 > news >正文

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. 栈区&#xff08;Stack&#xff09; 2. 堆区&#xff08;Heap&#xff09; 3. 数据段&#xff08;Data Segment&#xff09; 4. 代码段&#xff08;Code Segment&#xff09; 二、内存分配方式 1、静态内存分配 2、栈内分配 3、动态内存分配 &#x…...

Unity UGUI 之 Image和Rawimage

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 1.Image是什么 Unity - 手册&#xff1a;图像 精灵格式是什么&#xff1f; 1.2重要参数 …...

Lua 语法学习笔记

Lua 语法学习笔记 安装(windows) 官网&#xff1a;https://www.lua.org/ 下载SDK 解压&修改名称&#xff08;去除版本号&#xff09; 将lua后面的版本号去掉&#xff0c;如lua54.exe->lua.ext 配置环境变量 数据类型 数据类型描述nil这个最简单&#xff0c;只有值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&#xff0c;接口 /// <summary>/// Web页面支付/// </summary>public interface IWebPagePay{public WebPagePayResult CreatePay(string productName, string orderSn, string totalPrice);}2&#xff0c;实现接口 实现阿里支付 public class AliPagePay : IWe…...

Matlab 命令行窗口默认输出(异常)

目录 前言Matlab 先验知识1 异常输出的代码2 正常输出的代码 前言 在单独调试 Matlab 写的函数时出现不想出现的异常打印值&#xff0c;逐个注释排查才找到是 if elseif else 代码块的问题&#xff0c;会默认打印输出 else 部分第一个返回值的值&#xff08;下方代码中的 P值&…...

LeetCode/NowCoder-二叉树OJ练习

励志冰檗&#xff1a;形容在清苦的生活环境中激励自己的意志。&#x1f493;&#x1f493;&#x1f493; 目录 说在前面 题目一&#xff1a;单值二叉树 题目二&#xff1a;相同的树 题目三&#xff1a;对称二叉树 题目四&#xff1a;二叉树的前序遍历 题目五&#xff1a;另…...

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. 引言 图一. 新基建&#xff1a;低空经济 低空经济作为一种新兴的经济形态&#xff0c;涵盖了无人机、电动垂直起降飞行器&#xff08;eVTOL&#xff09;、低空物流、空中交通管理等多个领域。随着5G网络的普及和演进&#xff0c;5G RedCap&#xff08;Reduced Capability&a…...

MongoDB教程(二十一):MongoDB大文件存储GridFS

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、GridFS…...

vue 搜索框

效果 创建搜索组件&#xff1a; 在Vue项目中&#xff0c;首先需要创建一个搜索组件。这个组件通常包含一个输入框和一个搜索按钮。使用v-model指令将输入框与组件的数据属性&#xff08;如searchKeyword&#xff09;进行双向绑定&#xff0c;以便获取用户输入的关键词。处理搜索…...

国科大作业考试资料-人工智能原理与算法-2024新编-第五次作业整理

1、本题以井字棋(圈与十字游戏)为例练习博弈中的基本概念。定义X_n为恰好有n个X而没有O 的行、列或者对角线的数目。同样O_n为正好有n 个O的行、列或者对角线的数目。效用函数给 X_3=1的棋局+1, 给O_3=1的棋局-1。所有其他终止状态效用值为0。对于非终止状态,使用线性的 …...

C++五子棋(未做完,但能玩,而且还不错)

代码放下面了&#xff0c;关于步骤介绍的我以后再完善一下。 #include<bits/stdc.h> #include<cstdio> #include<cstdlib> #include<ctime> #include<windows.h> #include<stdlib.h> #include<time.h> #define random(x) (rand()%x…...

二分查找代码详解

二分查找代码实现 以下是完整的代码和解释&#xff1a; #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&#xff0c;下面的网址讲了这个标签的相关 https://www.cnblogs.com/huihuihero/p/12978903.html 导入此插件 https://ext.dcloud.net.cn/plugin?id364 使用 uni.request({// 本地文件url: "/static/互联网医院医师端用户协议.txt…...

韦东山嵌入式linux系列-具体单板的按键驱动程序(查询方式)

1 GPIO 操作回顾 &#xff08;1&#xff09;使能模块&#xff1b; &#xff08;2&#xff09;设置引脚的模式&#xff08;工作于GPIO模式&#xff09;&#xff1b; &#xff08;3&#xff09;设置GPIO本身&#xff08;输入/输出&#xff09;&#xff1b; &#xff08;4&…...

如何使用 API list 极狐GitLab 群组中的镜像仓库?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab &#xff1a;https://gitlab.cn/install?channelcontent&utm_sourcecsdn 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署…...

PHP设计模式-简单工厂模式

核心&#xff1a; 一、定义一个接口类里面写规定好的方法。 interface Message{public function send(array $params);public function getMessage(array $params);public function getCode(array $params);} 二、定义产品类 、产品类继承接口类 class AlliYunSms implements …...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

Java并发编程实战 Day 11:并发设计模式

【Java并发编程实战 Day 11】并发设计模式 开篇 这是"Java并发编程实战"系列的第11天&#xff0c;今天我们聚焦于并发设计模式。并发设计模式是解决多线程环境下常见问题的经典解决方案&#xff0c;它们不仅提供了优雅的设计思路&#xff0c;还能显著提升系统的性能…...

麒麟系统使用-进行.NET开发

文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的&#xff0c;如果需要进行.NET开发&#xff0c;则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET&#xff0c;所以要进…...

React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?

系列回顾&#xff1a; 在上一篇《React核心概念&#xff1a;State是什么&#xff1f;》中&#xff0c;我们学习了如何使用useState让一个组件拥有自己的内部数据&#xff08;State&#xff09;&#xff0c;并通过一个计数器案例&#xff0c;实现了组件的自我更新。这很棒&#…...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...