剖析函数传递参数选择引用或值的区别

如果对ESP,EBP,栈帧等概念比较陌生,推荐先去了解一下函数调用的过程。

推荐使用OD+VS自己实现一遍加深印象。(OD用来查看内存,VS用来看汇编代码和源代码的对应情况)

mov与lea的区别

指令:mov  操作对象:变量         有无[]没有区别,都是取值

指令:mov  操作对象:寄存器      有[]表示取地址,没有[]表示取值

指令:lea    操作对象:变量         有无[]没有区别,都是取地址

指令:lea    操作对象:寄存器      有[]表示取值,没有[]表示取地址

代码中把对于ebp的偏移操作当作变量

内置类型

代码

#include<cstdio>
int addv(int a,int b)
{
	int x=a,y=b;
	return x+y;
}
int addr(int&a,int&b)
{
	int x=a,y=b;
	return x+y;
}
int main()
{
	int a=1,b=2;
	printf("%d\n",addv(a,b));
	return 0;
}

传值方式

调用addv函数之前的栈情况

扫描二维码关注公众号,回复: 6152174 查看本文章

可见连续的两个mov,push把addv的参数a,b的值压入了栈中,此时调用函数的准备已经完成。

执行call,call分为两个过程,把当前EIP(指令指针寄存器)所指指令(即call指令)下一条语句的地址push到栈中,jmp到调用的函数入口。此时栈内情况。

addv函数内

可以直接使用mov取到参数的值,然后赋值给栈内局部变量。

传引用方式

调用addr前栈情况

传递的是参数的地址

addr函数内

由于参数是通过地址的形式传递给函数的,所以在函数内使用它们时需要额外的取地址操作。

自定义类型

代码

#include<cstdio>
class A
{
public:
	int x,y,z;
	A():x(1),y(2),z(3)
	{ }
	A(A&b):x(b.x),y(b.y),z(b.z)
	{ }
};
void addv(A a,A b)
{
	A x=a,y=b;
}
void addr(A&a,A&b)
{
	A x=a,y=b;
}
int main()
{
	A a,b;
	addv(a,b);
	return 0;
}

定义变量

把变量首地址保存在ecx中,调用构造函数,函数中会按着顺序依次赋值,结束后内存状态

传值方式

我们要在栈顶构造参数,方法是调用拷贝构造函数,注意这里和构造函数的调用方法是不一样的,首先把栈顶向上扩展sizeof(A)的空间,我们将在这块空间中构造要传递的参数。把栈顶放入ecx,这里的ecx和构造函数中的ecx起同样的作用,然后把拷贝的目标对象的地址作为参数push到栈内,调用拷贝构造函数。

当我们构造完参数之后,调用addv之前,栈的情况

两个完整的对象已经构造完成,作为传给addv的参数。

在addv函数内部

可见我们使用的是我们之前压入栈中的,完整的变量。这里的赋值操作和前边的步骤类似,只是使用的地址不同。

传引用方式

并没有构造对象,只是把当前栈内对象的地址当做参数传给了函数。

addr函数内部

反正调用拷贝构造函数的时候不论是拷贝的目标,还是我们所要构造的对象都是通过指针被我们操作的,我们只需要把上一步传来的地址再传给拷贝构造函数就可以了。

总结

对于内置类型,传引用并没有什么性能上的提升,反而在函数内使用参数时需要额外的取值操作,反而降低了速度。

对于自定义类型,传引用可以有效地避免中间对象的构造和析构,极大的提升了效率。

猜你喜欢

转载自blog.csdn.net/qq_33113661/article/details/89815426