C++函数传参的初步探索
admin
2024-05-23 11:26:38
0

       今日发掘一个愚蠢、有趣又头疼的bug,因函数传参引起。我设定了一个函数专门给对象赋值,犯了一个极大的错误,我将变量直接传给函数,这个函数执行结束后,对象内部变量直接全部初始化了。后来请师父来看,他发现了问题,这直接刷新我都函数传参的认知。所以赶紧补上这篇函数传参的知识,以C++源代码和汇编为例,简单探索C/C++传参。

目录

一、问题

1.1 问题描述

 1.2 解决方法

二、函数传参原理及方式

2.1 原理

2.2 方式

三、总结


一、问题

1.1 问题描述

        直接上代码

#include
#includeint global;
class Inner
{
public:int a;int c;
public:Inner() { a = 0; c = 0; }~Inner() { global = 10; }
};class Outter
{
public:Inner * m_inner;
public://Outter(Outter& outter) = delete;Outter() { m_inner = new Inner(); }~Outter() { if (m_inner) { delete m_inner; m_inner = NULL;} }
};
template
void test(T out)
{}
int main()
{Outter out;test(out);return 0;
}

        声明一个Outter变量,将变量以普通变量的身份传入test函数,等到函数执行后,out里原有的值全部没有了。这源自于函数执行后析构了变量。具体如下:

main 中,首先声明了一个 Outter 类型的变量 out,然后将它作为参数传递给了函数 test。函数 test 具有模板类型,因此可以接受任何类型的参数。当函数调用结束时,参数将被析构。因此,当 main 函数结束时,变量 out 将被析构,指针变量也将被初始化。

        为了看到这个析构过程,我使用IDA反汇编工具将exe转成汇编去看:

; Attributes: bp-based frame fpd=0F0h; void test(class Outter)
??$test@VOutter@@@@YAXVOutter@@@Z proc nearvar_28= qword ptr -28h
arg_0= qword ptr  10h
arg_8= qword ptr  18h; __unwind { // j___CxxFrameHandler3_0
mov     [rsp-8+arg_0], rcx
push    rbp
push    rdi
sub     rsp, 108h
lea     rbp, [rsp+20h]
mov     rdi, rsp
mov     ecx, 42h ; 'B'
mov     eax, 0CCCCCCCCh
rep stosd
mov     rcx, [rsp+110h+arg_8]
mov     [rbp+0F0h+var_28], 0FFFFFFFFFFFFFFFEh
lea     rcx, unk_140023028
call    j___CheckForDebuggerJustMyCode
nop
lea     rcx, [rbp+0F0h+arg_0] ; this
call    j_??1Outter@@QEAA@XZ ; Outter::~Outter(void)
lea     rsp, [rbp+0E8h]
pop     rdi
pop     rbp
retn
; } // starts at 1400118B0
??$test@VOutter@@@@YAXVOutter@@@Z endp

        这是test函数的汇编解释部分:

1. 初始化栈帧:通过push指令将rbp和rdi寄存器的值压入栈中,并使用sub挀令分配内存。

2. 调用j___CheckForDebuggerJustMyCode函数来检查调试器是否正在运行。

3. 调用Outter类的构造函数和__autoclassinit2函数来初始化Outter类的实例。

4. 调用test函数并传入Outter类实例。

5. 调用Outter类的析构函数销毁实例。

6. 检查栈中的变量并进行安全检查。

7. 恢复栈帧并返回。

        Outter的析构确实被调用了: j_??1Outter@@QEAA@XZ ; Outter::~Outter(void)

        可以总结以下了:传入给函数带指针对象的类对象,不传入它的引用或者指针,那么函数执行完析构参数时,会将其指针对象同样析构,这带来的影响是灾难的,原本初始化好的对象啪就这么没了。

 1.2 解决方法

        1. 可以禁止类的copy构造,函数接收到参数时会对执行拷贝构造,我们将其禁止,组织编译成功,coder就会修改,不会出现这样的问题。在类里加上这句禁止拷贝构造:

Outter(Outter& outter) = delete;

        2. 很简单,传入参数的引用即可:

template
void test(T& out)
{}

        3. 声明对象时直接创建对象的指针,避免对象在栈上。

Outter* out = new Outter(); 

二、函数传参原理及方式

2.1 原理

        C++函数接受参数主要有两种情况:传值和传引用。

        传值:函数会copy一份变量在函数作用域使用,不改变原参数。

3. 调用Outter类的构造函数和__autoclassinit2函数来初始化Outter类的实例。

4. 调用test函数并传入Outter类实例。

5. 调用Outter类的析构函数销毁实例。

        在传入参数前,确实对Outter进行了重构造,也印证了进行了copy。

        传引用:传入参数的地址,函数可以对其修改,引用作为函数参数时,引用本质上是对原变量的别名,并且在函数内部对引用进行的操作实际上是对原变量进行的操作。因此,引用参数在内存中与原变量存储在同一个位置。当然前面加const也可以禁止修改。一、中代码传引用时test函数部分汇编如下:

; Attributes: bp-based frame fpd=0D0h; void test(class Outter &)
??$test@VOutter@@@@YAXAEAVOutter@@@Z proc neararg_0= qword ptr  10h
arg_8= qword ptr  18hmov     [rsp-8+arg_0], rcx
push    rbp
push    rdi
sub     rsp, 0E8h
lea     rbp, [rsp+20h]
mov     rdi, rsp
mov     ecx, 3Ah ; ':'
mov     eax, 0CCCCCCCCh
rep stosd
mov     rcx, [rsp+0F0h+arg_8]
lea     rcx, unk_140023028
call    j___CheckForDebuggerJustMyCode
lea     rsp, [rbp+0C8h]
pop     rdi
pop     rbp
retn
??$test@VOutter@@@@YAXAEAVOutter@@@Z endp

这行代码读取了引用类型的参数(arg_8),并将其存储在 rcx 寄存器中。

mov rcx, [rsp+0F0h+arg_8]

        寄存器存储的是CPU中处理数据的临时存储单元,在函数内部对寄存器中的参数进行修改时,函数结束后会将寄存器中的值返回到调用函数中,并在调用函数中通过寄存器进行读取,以此实现对参数的修改。

        这个时原理层面的函数传参。

2.2 方式

  •         值传递:这是最常见的传递参数的方式,它实际上是把实参的值复制到形参。如果函数修改形参的值,不会影响实参。
  •         引用传递:引用是一个别名,把它传递给函数实际上是把实参的地址传递给函数,因此函数可以直接操作实参。
  •         指针传递:指针是一个存储地址的变量,如果把一个指针传递给函数,函数就可以通过指针来访问实参。

当函数以指针的形式接收参数时,传递的是该变量的地址。函数内部可以通过访问该地址上的值来更改实际变量的值,并且这个修改对于调用该函数的代码是可见的,因为修改的是实际变量。

  •         数组传递:C++不允许直接把数组作为参数传递给函数,但是可以把数组名作为指针传递给函数,函数可以通过指针来访问数组中的元素。

当向函数传递数组时,数组的首地址实际上被当作指针传递。因此,函数内部可以通过指针访问数组中的元素。但是,数组的大小需要通过另一种方式传递给函数,以便函数可以正确处理数组。

        这里不做过多赘述了。

三、总结

        一个传参引发的血案,这边给大家建议,简单的参数例如int、string等可以直接传。复杂的参数,比如类对象,建议传指针或引用。祝大家周末愉快!

相关内容

热门资讯

斗金订购APP贵金属期货投资被...   斗金订购APP的投资者被广告宣传给诱导,注册就送什么现金,然后充值返现金卷等等这些宣传方式,都是...
哈易购APP非法期货交易欺骗投...   哈易购APP宣传可做白银铂金贵金属订购交易,但实际上并没有取得相关交易资质!哈易购APP本质上就...
消息称百度旗下昆仑芯瞄准500... 6 月 29 日消息,据《The Information》昨日援引知情人士消息,百度旗下 AI 芯片...
打造夏日消费新场景 第35届北... 北京商报讯(记者 翟枫瑞)6月29日消息,第35届北京国际燕京啤酒文化节新闻发布会在京举行。本届啤酒...
社保基金持仓数据出炉,一季度增... 最近各大上市公司一季度财报都公开了,咱们国家社保基金的持仓数据也全部曝光。目前社保拿着比亚迪价值44...
36氪首发 | 海思、中兴团队... 作者 | 乔钰杰 编辑 | 袁斯来 硬氪获悉,广州宸思通讯科技有限公司(以下简称“宸思科技”)近日完...
两天蒸发47亿市值!一纸税务通... 一纸税务通知书,能让一家百亿龙头两天蒸发47亿市值。 6月22日,北大荒(600598.SH)公告称...
SK海力士将投资1100万亿韩... SK集团会长崔泰源6月29日在韩国“三大重大计划”发布会上宣布,公司将投资1100万亿韩元扩大半导体...
两只A股,终止上市! 两家A股公司,即将摘牌。 6月29日,退市沪科(600608.SH)公告称,上海证券交易所将在202...
原创 M... 一家成立近十年的自动驾驶公司,在IPO时吸引了14家基石投资者认购近一半的发行股份,其中不乏奔驰、比...
基金忠言|国寿安保滤镜碎,三年... 图片来源:视觉中国 蓝鲸新闻6月29日讯(记者 祁和忠)保险系基金公司国寿安保总经理换人了。 6月2...
三星电机计划加码玻璃基板!相关... 6月29日,玻璃基板概念股午后有所回升, 华工科技(000988.SZ)逼近涨停, 彩虹股份(600...
拉萨海关持续壮大外贸经营主体 ...   新华网拉萨6月28日电(记者蒋梦辰)近日,记者从拉萨海关获悉,今年前5个月,西藏有进出口实绩的外...
机构:二季报临近,医药生物板块... 6月29日,华源证券发布了一篇医药生物行业的研究报告,报告指出,业绩期临近,产业链景气度有望再次迎来...
每日收评科创50放量涨超4.5... 财联社6月29日讯,三大指数全线收红,创业板指探底回升,科创50指数大涨4.61%。沪深两市成交额3...
6月多地土拍结构性升温:深圳单... 进入2026年6月,不少城市核心区地块集中诞生高溢价宗地,热度突出的城市包含深圳、杭州、长沙。 其中...
业绩炸裂!盛达资源半年预盈3.... 6月29日,贵金属矿山龙头盛达资源(000603.SZ)发布 2026 年半年度业绩预告,上半年业绩...
A股午后拉升三大股指收涨:半导... A股三大股指6月29日开盘涨跌互现。早盘沪强深弱,创指一度跌超2%。半导体午后拉升,带动两市上涨,沪...
原创 空... 前言 大家好,我是老金。 这几天,两幅极度割裂的画面放在一起,把我看笑了。 一边是在持续的热浪下,欧...