C++ 指针变量做参数传递时的情况分析
前言
指针变量作为参数传递时,很容易混淆指针本身和指针指向的内容,实际应用中可能会导致无法预料的问题,所以做一下详细分析。
注意,在测试过程中为了看测试效果,有些指针变量分配了空间,但是未做回收,实际应用中要注意,分配空间后在合适的位置释放。
1. 指针变量直接作为参数传递
指针变量pVariant 作为实参传递给函数funcPointer,在funcPointer中由形参pParam接收
1.1 代码
void funcPointer(int *pParam)
{qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;// 修改指针所指向地址中的内容*pParam = 2;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;// 修改pParam的指向pParam = new int;*pParam = 3;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int *pVariant = new int;*pVariant = 1;qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;funcPointer(pVariant);qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;return a.exec();
}
1.2 输出结果:
1. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x1986708 pVariant值 1
2. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x1986708 pParam 值 1
3. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x1986708 pParam 值 2
4. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x1986848 pParam 值 3
5. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x1986708 pVariant值 2
1.2.1 结果分析:
- 从1,2可以看出,pVariant和pParam 本身的存储地址不相同,指向的地址和pVariant相同,说明pParam是pVariant的一个拷贝。
- 从2,3,5可以看出,通过函数funcPointer可以成功修改指针所指向的地址中的内容。
- 从1,3,4,5可以看出,修改形参pParam指向的地址后,不会影响实参pVariant。
综上,一级指针变量直接做为参数传递时,函数中会产生一个临时拷贝,通过这个拷贝可以修改原指针变量所指向地址的内容,但是不能改变原指针的指向。
1.3 在函数funcPointer最后增加回收空间操作:
delete pParam;pParam = nullptr;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam;
1.3.1 输出结果:
1. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x31265e8 pVariant值 1
2. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x31265e8 pParam 值 1
3. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x31265e8 pParam 值 2
4. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x3126768 pParam 值 3
5. pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x0
6. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x31265e8 pVariant值 2
从结果可以看出,如果形参pParam 执行new重新分配了空间,即此时pParam 和pVariant指向的地址不同,那么在函数中回收指针空间时只能操作pParam,不会回收指针变量pVariant的空间,所以此时想回收指针变量pVariant空间的话,需要在外部回收。
1.3.2 funcPointer函数中去掉pParam的new操作,增加delete
void funcPointer(int *pParam)
{qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;// 修改指针所指向地址中的内容*pParam = 2;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;delete pParam;pParam = nullptr;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int *pVariant = new int;*pVariant = 1;qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;funcPointer(pVariant);qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;return a.exec();
}
1.3.3 输出结果
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x19267e8 pVariant值 1
pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x19267e8 pParam 值 1
pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x19267e8 pParam 值 2
pParam 自身的地址: 0x77fdd0 pParam 指向的地址: 0x0
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x19267e8 pVariant值 1761789996
从结果可以看出,执行 delete pParam; pParam = nullptr;后,pVariant指向的空间也已经被回收,但是pVariant不为空,依然指向原来的地址,获取到了一个未定义的值,这样可能导致程序出现不可控的问题。
2.指针变量作为引用参数传递
2.1 代码
void funcRefPointer(int *&pParam)
{qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;*pParam = 2;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;pParam = new int;*pParam = 3;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam<<"pParam 值"<<*pParam;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int *pVariant = new int;*pVariant = 1;qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;funcRefPointer(pVariant);qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;return a.exec();
}
2.2 输出结果:
1. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x34665f8 pVariant值 1
2. pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x34665f8 pParam 值 1
3. pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x34665f8 pParam 值 2
4. pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x3466738 pParam 值 3
5. pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x3466738 pVariant值 3
2.2.1结果分析:
pParam 和 pVariant的存储地址、指向地址和内容完全一致。
指针变量作为引用传递时,形参pParam是指针变量pVariant的一个别名,对pParam的操作相当于对pVariant直接进行操作。引用传递时可以修改原指针的指向,可以修改指向地址的内容。
2.3在函数funcRefPointer最后增加回收空间操作:
delete pParam;pParam = nullptr;qDebug()<<"pParam 自身的地址:"<<&pParam<<"pParam 指向的地址:"<<pParam;
2.3.1输出结果:
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x3496638 pVariant值 1
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x3496638 pParam 值 1
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x3496638 pParam 值 2
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x3496878 pParam 值 3
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x0
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x0
从结果可以看出,引用传递指针变量时,在函数中所有对引用变量的操作,都会影响原来的指针,包括delete操作,所以引用传递时,如果确定此指针之后不再使用,那么可以在函数中释放空间并将指针置空。detele指针并置为nullptr后,无法再获取指针指向的内容。
3.使用二级指针传递
传递指针变量的地址
3.1 代码
void funcPPointer(int **pParam)
{qDebug()<<"pParam 自身的地址:"<<&(*pParam)<<"pParam 指向的地址:"<<*pParam<<"pParam 值"<<**pParam;**pParam = 2;qDebug()<<"pParam 自身的地址:"<<&(*pParam)<<"pParam 指向的地址:"<<*pParam<<"pParam 值"<<**pParam;*pParam = new int;**pParam = 3;qDebug()<<"pParam 自身的地址:"<<&(*pParam)<<"pParam 指向的地址:"<<*pParam<<"pParam 值"<<**pParam;
}int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);int *pVariant = new int;*pVariant = 1;qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;funcPPointer(&pVariant);qDebug()<<"pVariant自身的地址:"<<&pVariant<<"pVariant指向的地址:"<<pVariant<<"pVariant值"<<*pVariant;return a.exec();
}
3.2 输出结果
pVariant自身的地址: 0x77fdec pVariant指向的地址: 0x35166c8 pVariant值 1
pParam 自身的地址: 0x77fdec pParam 指向的地址: 0x35166c8 pParam 值 1
pParam 自身的地址: 0x77fdec pParam 指向的地址: 0x35166c8 pParam 值 2
pParam 自身的地址: 0x77fdec pParam 指向的地址: 0x3516748 pParam 值 3
pVariant自身的地址: 0x77fdec pVariant指向的地址: 0x3516748 pVariant值 3
二级指针传递时,输出结果和引用传递一样,因为都相当于传递了原指针的地址。
3.3 在函数funcPPointer最后增加:
delete (*pParam);(*pParam) = nullptr;qDebug()<<"pParam 自身的地址:"<<&(*pParam)<<"pParam 指向的地址:"<<*pParam;
3.4 输出结果:
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x34967a0 pVariant值 1
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x34967a0 pParam 值 1
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x34967a0 pParam 值 2
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x3496730 pParam 值 3
pParam 自身的地址: 0x77fe04 pParam 指向的地址: 0x0
pVariant自身的地址: 0x77fe04 pVariant指向的地址: 0x0
增加delete后的输出结果也和引用传递时相同。所以,指针变量作为引用和二级指针的方式传递时,效果相同。
注意事项
1. delete指针后,如果不将指针置为nullptr,会产生野指针,指针仍会指向原来的地址,再次使用可能会导致程序崩溃,或者会得到一个不确定的值,导致程序产生不可控的结果。
2. 使用引用传递或者二级指针传递时,在函数中重新分配空间之前,需要将上一次new分配的空间回收,否则会造成内存泄漏。也就是多次new的时候需要对应的delete。
3. 如果需要使用调用接口的方式释放指针空间时,需要使用二级指针或者引用传递指针变量的方式,也就是直接传递指针变量的地址。如果直接使用指针传递,能够正常回收空间,但是无法将实参指针置为空值,导致其成为野指针,从而引起内存问题。
相关文章:
C++ 指针变量做参数传递时的情况分析
前言 指针变量作为参数传递时,很容易混淆指针本身和指针指向的内容,实际应用中可能会导致无法预料的问题,所以做一下详细分析。 注意,在测试过程中为了看测试效果,有些指针变量分配了空间,但是未做回收&am…...
Linux环境下Oracle 11g的离线安装与配置历程
在成功体验了 Windows 版本的Oracle 11g 后,这几天心血来潮,决定再挑战一下Linux 环境下的安装,特别是在考虑到部门内部虚拟机无法联网的情况下,我选择了在CentOS 7上进行离线安装。这次安装之旅,主要参考了下面大佬的…...
上位机图像处理和嵌入式模块部署(mcu项目2:串口日志记录器)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 淘宝上面有一个商品蛮好玩的,那就是日志记录器。说是记录器,其实就是一个模块,这个模块的输入是一个ttl串口&am…...
容器是线程不安全的,如果多线程下不加锁直接使用容器会发什么
可能存在的问题 数据竞争 当两个或多个线程同时读写同一个容器且至少有一个线程在写时,会导致数据竞争。这种情况下,容器的内部状态可能会被破坏,从而导致未定义行为。这些未定义的行为包括数据损坏,程序崩溃,以及无…...
配置光源——笔记
一、灯光的类型 (一)Directional Light(定向光) 1、只改变方向变化,不记录位置变化 2、相当于太阳光 3、室外一般使用 (二)Spot 聚光灯:昏暗(凌晨或傍晚),有一个光斑…...
Java---SpringBoot详解一
人性本善亦本恶, 喜怒哀乐显真情。 寒冬暖夏皆有道, 善恶终归一念间。 善念慈悲天下广, 恶行自缚梦难安。 人心如镜自省照, 善恶分明照乾坤。 目录 一,入门程序 ①,创建springboot工程&#…...
MFC扩展库BCGControlBar Pro v35.0 - 可视化管理主题等全新升级
BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中,并为您节省数百个开发和调试时间。 BCGControlBar专业版 v35.0已全新发布了,这个版本改进类Visual Studio 2022的视觉主题、增强对多个…...
Springboot 配置 log4j2 时的注意事项
感谢博主 https://www.cnblogs.com/fishlittle/p/17950944 依赖 SpringBoot 的 spring-boot-starter/ spring-boot-starter-web 自带的是 logback 日志,若要使用 log4j2 日志,需要引入对应依赖。logback 日志和 log4j2 日志都是对 slf4j 门面的实现&am…...
微服务-初级篇
微服务-初级篇 认识微服务1.1 单体架构1.2 分布式架构1.3 微服务 SpringCloud2.1 了解2.2 服务拆分原则2.3 服务拆分效果 Nacos注册中心3.1 认识和安装Nacos3.1.1 Nacos下载3.1.2 Nacos安装 3.2 服务注册到Nacos Feign远程调用4.1 Feign引入4.2 Feign配置 认识微服务 1.1 单体…...
批量制作word表格
问题背景 将excel表中的成绩内容制作为成绩单,每页对应一个学员的成绩,方便打印 代码实现 ## 导入包 import pandas as pd from docx import Document from docx.enum.text import WD_ALIGN_PARAGRAPH,WD_LINE_SPACING# 读取 Excel 内容 df pd.read_e…...
代码随想录算法训练营:27/60
非科班学习算法day27 | LeetCode455:分发饼干 ,Leetcode376:摆动序列 ,Leetcode53:最大子数组和 介绍 包含LC的两道题目,还有相应概念的补充。 相关图解和更多版本: 代码随想录 (programmercarl.com)https://programmercarl.c…...
Redis 中String类型操作命令(命令演示,时间复杂度,返回值,注意事项)
String 类型 文章目录 String 类型set 命令get 命令mset 命令mget 命令get 和 mget 的区别incr 命令incrby 命令decr 命令decrby 命令incrbyfloat 命令append 命令getrange 命令setrange 命令 字符串类型是 Redis 中最基础的数据类型,在讲解命令之前,我们…...
2024亚太杯中文赛B题洪水灾害的数据分析与预测原创论文分享
大家好,从昨天肝到现在,终于完成了2024年第十四届 APMCM 亚太地区大学生数学建模竞赛B题洪水灾害的数据分析与预测的完整论文啦。 实在精力有限,具体的讲解大家可以去讲解视频: 2024亚太杯中文赛B题洪水灾害预测原创论文保姆级教…...
Oracle 19c 统一审计表清理
zabbix 收到SYSAUX表空间告警超过90%告警,最后面给出的清理方法只适合ORACLE 统一审计表的清理,传统审计表的清理SYS.AUD$不适合,请注意。 SQL> Col tablespace_name for a30 Col used_pct for a10 Set line 120 pages 120 select total.…...
PostgreSQL(二十二)缓冲区管理器
目录 一、缓冲区概述 1、缓冲区结构 2、buffer_tag结构 3、Backend进程读取操作 4、写脏块 二、缓冲区管理器结构 1、第一层:Buffer Table layer(缓冲区表层) 2、第二层:Buffer Descriptor Layer(缓冲区描述层…...
流程制造业与离散制造业有何差异?流程行业智能制造关注什么?
在当今快速发展的工业领域,智能制造已经成为推动制造业转型升级的关键力量。随着“工业4.0”概念的提出,智能制造的理念和技术被广泛应用于各个制造行业,包括离散制造业和流程制造业。尽管智能制造的起源和发展在很大程度上受到了离散制造业的…...
【论文速读】《面向深度学习的联合消息传递与自编码器》,无线AI的挑战和解决思路
这篇文章来自华为的渥太华无线先进系统能力中心和无线技术实验室,作者中有大名鼎鼎的童文。 一、自编码架构的全局收发机面临的主要问题 文章对我比较有启发的地方,是提到自编码架构的全局收发机面临的主要问题: 问题一:基于随…...
C++从入门到起飞之——输入输出!
目录 1.命名空间 1.1namespace的价值 1.2namespace的定义 1.3命名空间使⽤ 2.C输⼊&输出 3.完结散花 个人主页:秋风起,再归来~ C从入门到起飞 个人格言:悟已往之不谏,知来者犹可追 克心守己…...
米文AD10配置gmsl摄像头操作
一、进入桌面快捷方式 0、设置摄像头型号 miivii_websettings.desktop 设置摄像头 1、获取camera信息 cat /var/log/gmsl_camera.lognvidiamiivii-tegra:~$ cat /var/log/gmsl_camera.log attestationVerify [13] succeed. [INFO ]: miivii gmsl service start! [INFO ]: V…...
【Selenium配置】WebDriver安装浏览器驱动(ChromeEdge)
【Selenium配置】WebDriver安装浏览器驱动(Chrome&Edge) 文章目录 【Selenium配置】WebDriver安装浏览器驱动(Chrome&Edge)Chrome确认Chrome版本下载对应driver把解压后的chromedriver文件放在chrome安装目录下࿰…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
SpringCloud——Nacos
1、核心功能: 服务注册与发现: 服务实例可动态注入到Nacos中,消费者通过服务名发现可用实例。 // 启用EnableDiscoveryClient注解启用Nacos SpringBootApplication EnableDiscoveryClient public class UserServiceApplication {public st…...
二叉树-226.翻转链表-力扣(LeetCode)
一、题目解析 翻转可以理解为树的左右子树交换,从根到叶子节点,但是这里交换的是链接的指针,而不是单纯的交换值,当出现nullptr时,也是可以交换链接的,交换值的话就不行了。 二、算法原理 依旧的递归&…...
