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等可以直接传。复杂的参数,比如类对象,建议传指针或引用。祝大家周末愉快!

相关内容

热门资讯

盘前:科技股热潮降温 纳指期货... 来源:环球市场播报 周五,美国股指期货下跌。科技股走弱、美国国债收益率上升拖累大盘。科技板块近期大...
600096,拟投建1000万... 今日(5月15日),三大股指均收跌,全市场成交额为3.37万亿元,较上一个交易日缩量179亿元。收盘...
原创 应... 当地时间5月14日美股盘后,半导体设备达成应用材料(Applied Materials)公布了202...
歌手温岚被紧急送入ICU,主办... 歌手温岚原定于5月16日在上海举办巡回演唱会。15日,有消息称温岚因身体不适被紧急送医,随后,演唱会...
闪迪、美光越涨越便宜?股价暴涨... 存储芯片需求的爆炸式增长正在颠覆传统估值逻辑——股价越涨,闪迪和美光反而越便宜。 闪迪今年以来股价累...
监管部门“5·15”密集发声,... 监管新规密集发布,投资者保护防线再加固。 5月15日,证监会在北京举办2025年“5·15全国投资者...
纳指、标普500指数续创新高!... 美股三大指数集体收涨,纳指涨0.88%,标普500指数涨0.77%,道指涨0.75%。其中,纳指、标...
欧洲主要股指收盘集体下跌 英国富时100指数跌1.71%,法国CAC40指数跌1.72%,德国DAX30指数跌2.11%,富时...
巴宝莉去年扭亏盈利近两亿元,进... 英国奢侈品牌Burberry巴宝莉公布截至3月28日的2026财年业绩,释放明显复苏信号。集团营收同...
腾澎投资拟减持巨人网络不超3%... 巨人网络公告显示,公司控股股东一致行动人、第二大股东上海腾澎投资合伙企业(有限合伙)(下称“腾澎投资...
医疗健康领域投融资日报(5月1... 据亿欧数据统计,昨日(2026年5月14日)共披露23起投融资事件,涉及15家国内企业,8家国外企业...
债市ETF“工具箱”,解锁固收... 当前,市场波动有所加大,不确定性因素较多,单一资产投资模式难以有效应对市场起伏,引入固收类资产、优化...
招商蛇口股东会通过博时蛇口产园... 观点网讯:5月15日,招商蛇口2026年第一次临时股东会在公司总部会议室召开,会议由董事长朱文凯主持...
《学习时报》刊文:全球海洋可再... 海洋可再生能源一般指蕴藏于海水水面、水体及海床之中,可转化为电能的清洁能源类型,主要包括海上风能、潮...
数据看盘游资、量化抢筹多只机器... 沪深股通今日合计成交4353.39亿,其中澜起科技和中际旭创分居沪股通和深股通个股成交额首位。板块主...
土耳其BIST-100指数下跌... 土耳其BIST-100指数下跌1.8%,主要银行指数下跌2.4%。 来源:金融界AI电报
15分钟动态电价时代:园区光伏... 一、电价改革的“加速度”:从分时计费到现货波动 过去,工商业用户的电价表一年可能只调整几次,峰、平、...
湘潭上元产业港:多套成交 12... 湘潭上元产业港再迎成交热潮,近期3套优质厂房成功签约,多位企业家携手落子,以实力见证长株潭热土的产业...
4月新增人民币贷款跌入负区间,... 本报(chinatimes.net.cn)记者刘佳 北京报道 作为观察货币政策传导效率的核心窗口,4...
2.2/7.2馆展位图首发!5... 【2.2馆展位图】 【7.2馆展位图】 Bakery china 2.2馆部分 企业推介 22B...