C生万物 | 常见的六种动态内存错误

news2025/2/27 7:20:34

在这里插入图片描述

学习过C语言中的动态内存函数,例如【malloc】、【calloc】、【realloc】、【free】,那它们在使用的过程中会碰到哪些问题呢,本本文我们一起来探讨下~

1、对NULL指针的解引用操作

代码:

void test()
{
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;	//如果p的值是NULL,就会有问题
    free(p);
}

分析:

  • 首先看到第一个,你要知道的是INT_MAX是什么。它是一个宏定义,表示int类型(整型)能够表示的最大值,其值为2147483647,那在上面讲malloc的时候我们有说到过,若是需要申请的空间过大的话可能就会导致申请失败的问题,所以这里很致命的一个错误就是在申请空间之后没有去及时判断是否申请成功
  • 可以看到编译器也是给我们报出了一个Warning警告说:⚠ 取消对NULL指针的引用

在这里插入图片描述
改进:

  • 此时我们就可以对代码去做一个改进,对malloc之后的返回值做一个判断
void test()
{
    int* p = (int*)malloc(INT_MAX / 4);
    if (NULL == p)
    {
        perror("fail malloc");
        exit(-1);
    }
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);
}
  • 这个时候我们就可以看到没有警告再报出来了

在这里插入图片描述

2、对动态开辟空间的越界访问

代码:

int main(void)
{
	int* p = (int*)malloc(100);
	if (NULL == p)
	{
		perror("malloc fail");
		exit(-1);
	}

	int i = 0;
	for (int i = 0; i < 100; i++)
	{
		*(p + i) = 0;	// 当i == 25时便会越界
	}
	free(p);
	p = NULL;
	return 0;
}

分析:

  • 接下去我们来看这个越界访问的问题,首先我们使用malloc向堆区申请了100个字节的空间,但是呢在下面对这块空间进行访问的时候却访问了100个整型的大小,此时一定会造成访问越界的问题
  • 但是呢口说无凭,我们一样通过调试来进行一个观察,不过这里在进行循环的时候i没有到100的话是不会出问题的,所以为了方便调试我们需要去设置一个【条件断点】,将i从【24】开始执行,这样我们很快就能观察到结果了

在这里插入图片描述

  • 然后我们便可以通过调试去进行观察了,可以看到i并没有到达100,而是直接跳出了当前循环,然后在free()的时候就出现了问题,一般我们在一些其他地方观察不到的问题就会在free()的地方显现出来,因为此时是要去释放掉我们的这块申请的空间了,便会引发一些异常

在这里插入图片描述

  • 其实我们可以将*(p + i) = 0修改成p[i] = 0,利用[]操作符对某个下标进行访问,此时我们可以看到编译器就报出了警告说索引"99"超出了“0"至”24"的有效范围,因此100个字节的空间只能供25个整型来进行存放,因此合法的下标索引即为0 ~ 24

在这里插入图片描述

改进:

  • 代码修改这一块的话我们只需要在申请空间的时候保证申请到足够的、正确的容量即可
int* p = (int*)malloc(100 * sizeof(int));
  • 这个时候我们就可以看到没有警告再报出来了

在这里插入图片描述

3、对非动态开辟内存进行free释放

代码:

void test()
{
	int a = 10;
	int* p = &a;
	free(p);	//ok?
}

分析:

  • 接下去再来看第三个,这里是对非动态开辟的内存进行free()释放,那我们在介绍free()的时候说到它只能释放由【malloc】、【calloc】、【realloc】所开辟出来的空间,这些空间都是在堆区上进行申请的,但是我们在普通的函数中所创建的普通变量无非是栈区或者静态区的,它们的释放工作并不是由free()来完成的,因此强行去这样做的话就会造成了一个很大的问题
  • 可以看到一样出现了我们刚才那样类似的问题

在这里插入图片描述

改进:

  • 本代码并没有什么通用的改进办法,如果不想出现问题的话就不要free()普通栈区上的变量即可,或者按照常规去动态申请然后在进行free()

4、使用free释放一块动态开辟内存的一部分

代码:

void test()
{
    int* p = (int*)malloc(100);
    if (NULL == p)
    {
    	perror("malloc fail");
    	exit(-1);
    }
    for (int i = 0; i < 10; i++)
    {
        p++;
    }
    free(p);    //p不再指向动态内存的起始位置
}

分析:

  • 本题的情境是这样的,我们在堆区申请了100个字节后,让指针p指向这块地址的起始位置,然后让其偏移了10个整型的位置,即40B的大小,那么此时指针p其实就指向了当前这一块地址的中间位置,那么此时再去free的时候其实就会出问题
  • 因为该函数在释放动态申请的内存时需要从这块地址其实位置开始,然后释放制定的字节数,若是从某个中间位置开始的话就不对了

从下图可以看出,因为free()函数需要做到申请多少释放多少,所以当其释放了一部分之后,就不够了,便造成了访问内存错误的问题
在这里插入图片描述

  • 一样,我们通过调试去进行观察,首先在一开始申请出这块空间的时候先记录一下初始位置的地址,然后我们便可以观察到其进行了一个偏移,

在这里插入图片描述

  • 可以看到,此时若是去free()的话就会出现警告,很明显这个debug_heap.cpp就是【堆】这一块出的问题

在这里插入图片描述

改进:

  • 要如何改进的话就会不要去free()一块动态开辟出来内存的一部分,而是要从起始地址开始释放,申请多少释放多少

5、对同一块动态内存多次释放

代码:

void test()
{
    int* p = (int*)malloc(100);
    //使用...
    free(p);

    //...
    free(p);	//重复释放
}

分析:

  • 这一点的话就是在我们释放完一块内存空间后忘了,然后再去对其进行了一次释放,这种操作的话其实也是很危险的,当我们在第一次释放的时候p所指向的那块空间的使用权已经还给操作系统了,但是呢我们并没有对这个指针p做置空的操作,于是它还指向那块空间所在的地址,不过里面的内容已经是随机的了,那么这个指针就是一个【野指针
  • 此时再对其做一个free()的操作,就会造成操作野指针的问题

在这里插入图片描述

改进:

  • 此时我们就可以对代码去做一个简单的改进,在第一次free后将指针p置为NULL即可,此刻若是后面再去free的话,就不会出现问题了,因为当我们传递NULL作为参数的时候,free(NULL)便不会去做任何的事情
void test()
{
    int* p = (int*)malloc(100);
    //使用...
    free(p);
    p = NULL;   // 将不使用的指针置为NULL
    //...
    free(p);	//重复释放
}

6、动态开辟内存忘记释放(内存泄漏)

代码:

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}

int main()
{
	test();
}

分析:

  • 那最后一个呢就是我们最常见的,在动态开辟内存后忘记去释放了,例如上面有一个test()函数,函数内部去申请了100个字节的数据,并为其做了一个初始化,此时main函数就正常地去调用它,但是呢这中间却没有任何地free()释放操作,就会存在【内存泄漏】的问题

💬 那有同学说:既然函数内部没有做释放的话我在调用结束后去free一下这个p不就好了

  • 这句话其实就存在很大的问题,如果读者有看过我的函数栈帧一文的话,就会很清楚了,对于一个在一个函数创建的变量,是处在当前这个函数所维护的栈帧中的,所以当这个函数调用结束后局部变量就会随着栈帧的销毁而不复存在,那此时我们再想去free()释放这块空间的时候,是无法访问到这个指针p的。因此要释放的话只能在函数内部进行才可以

改进:

  • 那改进这一块的话我们只需要在函数调用结束前去将其释放即可,不过别忘了在free()之后要将指针置为NULL防止野指针
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	
	free(p);
	p = NULL;
}
  • 所以当我们在使用动态内存的时候,一定要保证在【malloc】之后及时【free】,此时才能保证不会内存泄漏

但是它们两个成对出现就一定不会出现问题吗?

  • 我们来看看下面这段代码,可以看到中间有一个if(1)的条件判断,我们知道这个条件是天然成立的,然后看到当这个条件成立后就会执行return语句,那么当前这个函数就会结束了,此时并没有运行到free(p)这句话
  • 那么聪明的你一定很快反应过来了,即使是存在【malloc】和【free】成对出现的情况下,可能也无法百分百保证不会产生内存泄漏的问题,所以还是需要我们在写程序的时候多注意细节🤗
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
	if (1)
		return;		// 因为某些条件中途return了, 没到free()

	free(p);
}

int main()
{
	test();
}

总结与提炼

最后来总结一下本文所学习的内容

  • 通过上面的六个案例,我们总共了解到了六种动态内存错误的形式,分别是
    • 对NULL指针的解引用操作】 —— 操作空指针是非常危险的一件事,记得判空哦
    • 对动态开辟空间的越界访问】 —— 有多少就拿多少,不要贪心哦
    • 对非动态开辟内存进行free释放】 —— 请正确分类,送它去该去的地方
    • 使用free释放一块动态开辟内存的一部分 】—— 借了多少还多少,不要私藏哦
    • 对同一块动态内存多次释放】 —— 借了多少还多少,不要私藏哦
    • 动态开辟内存忘记释放】 —— 借了别人的东西要记得还
  • 在使用动态内存函数开辟出空间后,使用的时候一定要牢记以上几点,否则要出大问题的!

以上就是本文要介绍的所有内容 ,感谢您的阅读🌹

在这里插入图片描述

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

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

相关文章

Sui链上事务处理概述

Sui通过其混合式交易处理方法&#xff0c;实现比其他区块链更快速和高效的性能。这种方法使得Sui的交易测试吞吐率达到每秒297,000次。从实际应用的角度来看&#xff0c;使用Sui的用户在apps和游戏中几乎能够获得实时响应。 在区块链世界中&#xff0c;交易是apps运作的基础&a…

AutoHotKey脚本的循环:While和Loop

While AHK提供三种循环方法&#xff0c;分别是Loop, While和For&#xff0c;其中While和For在其他语言中都很常见&#xff0c;下面通过while循环&#xff0c;实现一个鼠标框选矩形尺寸的脚本 ; 来自官网的案例 CoordMode "Mouse", "Screen"~LButton:: {M…

【文献分享】基于感知质量的滚动优化无人机导航

论文题目&#xff1a;Perception-aware Receding Horizon Navigation for MAVs 作者&#xff1a;Zhang, Zichao ; Scaramuzza, Davide 作者机构&#xff1a;苏黎世大学 论文链接&#xff1a;https://files.ifi.uzh.ch/rpg/website/rpg.ifi.uzh.ch/html/docs/ICRA18_Zhang.pd…

【雕爷学编程】Arduino动手做(122)---BH1750光照传感器

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

使用近10年,说说向大家推荐Linux系统的理由

使用linux已经快十年了&#xff0c;一直想推荐身边的使用linux,但是一直没有分享。但是现在我想分享推荐了。下面我们一起聊聊为什么我向大家推荐linux。 为什么现在我想推荐了呢&#xff1f;是因为我自认为相对于同龄人来说&#xff0c;我使用桌面版的时间算是挺长的了&#…

彻底搞懂什么是阿里云服务器vCPU?

阿里云ECS服务器vCPU和CPU是什么意思&#xff1f;CPU和vCPU有什么区别&#xff1f;一台云服务器ECS实例的CPU选项由CPU物理核心数和每核线程数决定&#xff0c;CPU是中央处理器&#xff0c;一个CPU可以包含若干个物理核&#xff0c;通过超线程HT&#xff08;Hyper-Threading&am…

走向计算机视觉的通用人工智能:从GPT和大型语言模型中汲取的经验教训 (下)...

点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 论文地址&#xff1a;https://arxiv.org/pdf/2306.08641.pdf 计算机视觉研究院专栏 Column of Computer Vision Institute 人工智能…

Matlab预测模型-灰色预测模型

预测模型-灰色预测模型 灰色预测是对既含有已知信息又含有不确定信息的系统进行预测&#xff0c;就是对在一定范围内变化的、与时间有关的灰色过程进行预测。灰色预测对原始数据进行生成处理来寻找系统变动的规律&#xff0c;并生成有较强规律性的数据序列&#xff0c;然后建立…

Linux学习[19]管线命令详解1---cut, grep, sort, wc, uniq

文章目录 1. 何为管线2. 摘取命令&#xff1a;cut, grep2.1 cut2.2 grep 3. 排序命令sort,wc,uniq3.1 sort3.2 uniq3.3 wc 总结 1. 何为管线 管线命令和Linux学习18里面的连续执行指令少许不同。他是只有在前面指令执行正确的时候&#xff0c;才会执行管线命令。 即这个管线命…

基于Vue+Node.js的宠物领养网站的设计与开发-计算机毕设 附源码 83352

基于VueNode.js的宠物领养网站的设计与开发 摘 要 随着互联网大趋势的到来&#xff0c;社会的方方面面&#xff0c;各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去&#xff0c;而其中最好的方式就是建立网络管理系统&#xff0c;并对其进行信息管理。由…

彻底卸载mysql的详细步骤

目录 一、前言 二、操作步骤 &#xff08;一&#xff09; 停止mysql的服务 &#xff08;二&#xff09;控制面板卸载 &#xff08;三&#xff09;清除残留的文件 &#xff08;四&#xff09;删除注册表内容 &#xff08;五&#xff09;删除MySQL环境变量 一、前言 卸载…

spider-flow新手暴力入门

1.入口 链接跳转: spider-flow 或者本地有git软件直接输入gittt中的链接地址&#xff0c;回车键梭哈即可 2.环境部署&#xff0c;必备jdk8mysql idea打开项目&#xff0c;重点修改如下 mysql用小皮软件&#xff08;phpstudy&#xff09;自带的mysql5,navicat软件导入sql文…

SEO一般多久时间才会有效果?

&#x1f482; 个人网站:【海拥】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 目录 前言什么是SEOSEO的时间…

Sui x KuCoin Labs夏季黑客松第二批入围项目公布

Sui x KuCoin Labs夏季黑客松仍在如火如荼地进行中。自第一批入围项目名单公布后一周&#xff0c;第二批入围项目现已经过审核&#xff0c;入围最终Demo Day。 第二批入围名单 Vimverse Vimverse是一个基于Sui构建的创新生态金融平台&#xff0c;旨在释放去中心化储备货币协…

计算物理专题:主值积分

计算物理专题&#xff1a;主值积分 吴式枢理论 主值积分 设在以及上可积&#xff0c;其中为任意小的正数&#xff0c;当两者独立地趋于零&#xff0c;极限存在。如果&#xff0c;这个极限存在&#xff0c;则称它为瑕积分的主值&#xff0c;记作无界函数的改造则该瑕积分的主值…

使用鲁棒优化的定价策略进行微电网不平衡管理研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

童安格杀猪和金钥匙-UMLChina建模知识竞赛第4赛季第7轮

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 本轮每题分数较少&#xff0c;需最先答对所有4题&#xff0c;才能获得本轮优胜。 所有题目的回答必须放…

Python 进阶(一):Python连接MySQL数据库和CRUD操作

MySQL数据库的连接和CRUD操作 前言Python连接MySQL的五种方式1. 安装mysql-connector-python2. 连接数据库3. 查询数据4. 插入数据5. 更新数据6. 删除数据 前言 本文基于MySQL8.x版本的学习&#xff0c;python版本基于当前最新的3.x&#xff0c;windows操作系统下mysql的安装流…

对骨架进行去毛刺处理

文章目录 0. 效果1. 基本内容2. 参考 0. 效果 红色&#xff1a;端点 绿色&#xff1a;节点 蓝色线条&#xff1a;毛刺&#xff08;根据长度定义&#xff09; 1. 基本内容 本文中的端点和节点的获取是依据论文《一种有效的骨架毛刺去除算法》中的内容提取的。 端点&#…

Chromium多进程架构,你知道多少?

一、前言 国内外主流的浏览器&#xff0c;大多采用的是谷歌的Chromium 浏览器内核&#xff0c;Chromium是一个多进程多线程架构的Web引擎&#xff0c;很多应用和底层开发者希望了解Chromium中的进程和线程的种类和用途&#xff0c;以便能利用相关信息提升应用的性能。为此&…