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

相关内容

热门资讯

疑似新模型海外惊艳!智谱再度飙... 格隆汇2月10日|延续昨日强势,港股市场AI概念股今日再度集体走强,其中,“全球大模型第一股”智谱(...
原创 特... 特朗普上任已逾一年,他推行的关税政策像一阵狂风,搅动了全球的经贸秩序。对于美国经济的未来走向,诺贝尔...
原创 一... 2026年2月9日晚的美股市场,上演了一场让很多投资者既兴奋又意外的行情。 本以为大涨之后总要歇一歇...
电商领域侵权问题获关注,知识产... 2月10日,知识产权保护概念持续拉升,截至发稿,成分股读客文化(301025.SZ)、中文在线(30...
原创 1... 12艘满载着俄罗斯乌拉尔原油的超级油轮,正像一群迷路的巨鲸,散落在从马六甲海峡到中国南海的广阔水域里...
凯思凯迪完成近5亿融资:中平资... 雷递网 乐天 2月10日 凯思凯迪宣布近期完成近5亿元新一轮融资,本轮融资由中平资本领投,国寿资本、...
美国出现小米YU7测试车?雷军... 近日,网上传出小米YU7 MAX测试车出现在美国道路的消息,难不成小米汽车要进军美国市场了? 事实...
2026-2032年中国食糖行... 共研网发布的《2026-2032年中国食糖行业深度调研与市场调查预测报告》共十二章。首先介绍了食糖行...
原创 美... 特朗普上台后不久,便对进口产品挥起了关税大棒。从钢铝到汽车零部件,一系列严苛的关税政策自2025年春...
盘中必读|字节旗下Seedan... 2月10日,AI短剧概念延续强势,荣信文化(301231)、捷成股份(300182)、欢瑞世纪(00...
2月25日起预约!申请退税别错... 近日,国家税务总局发布通告,明确2025年度个人所得税综合所得汇算清缴办理时间为2026年3月1日至...
再迎反弹!现货黄金重回5000... 贵金属再迎反弹。 2月9日,黄金、白银价格同步拉升。现货黄金再次突破关键阻力位,重回5000美元/盎...
YU7现身加州高速,小米会不会... 2月10日,雷军发文: 前段时间,一辆YU7行驶在美国加州的高速公路上,挂着当地的测试车牌。 很多人...
宁波迎来开年第一股!爱芯元智港... 转自:东南财金 2月10日,爱芯元智(0600.HK)正式于港交所主板挂牌上市,成为港股边缘计算AI...
2026年春节档新片预售票房已... 2月10日,市场早盘窄幅震荡,三大指数小幅下跌,北证50指数盘中跌超1%。沪深两市半日成交额1.39...
原创 俄... 俄罗斯黄金大量涌入中国,这背后究竟隐藏了怎样的玄机?根据2025年海关的数据,单单实物净进口量就高达...
亚太药业:聘任邱中勋为公司总经... 每经AI快讯,亚太药业2月9日晚间发布公告称,因公司控制权已发生变更,根据《股份转让协议》约定等相关...
原创 中... 我们中国的女富豪中,不乏靠着刻苦努力一步步爬上顶端的典型,也有不少依靠精准眼光与幸运投资一跃而成的成...
黄金交易提醒:美元疲软+央行“... 汇通财经APP讯——2026年2月的第二个星期,全球金融市场的心脏,似乎正随着那剧烈跳动。金价在50...
多措并举推动投资止跌回稳 国家统计局数据显示,2025年,全国固定资产投资同比下降3.8%。分领域看,基础设施投资下降2.2%...