C/C++的参数传递机制
近来公司招人较多,由此面试了非常多的C++程序员。面试时,我都会问到参数传递的相关问题,尤其侧重指针。因为指针毕竟是C/C++最重要的一个优势(在某种情况下也可以说是劣势)。但其结果是,1/3的人基本上讲错了,1/3的知其然却不知其所以然。所以我觉得有必要把这些知识点梳理下,分享出来。(下面的讨论都是基于VS和GCC的默认编译方式,其他特殊编译方式不在本文作用范围内。)
C/C++函数参数的传递方式有三种:值传递(pass by value)、指针传递(pass bypointer)、引用传递(pass by reference)。
C/C++函数参数的传递通道是通过堆栈传递,默认遵循__cdecl(C声明方式),参数由调用者从右往左逐个压入堆栈,在函数调用完成之后再由调用者恢复堆栈。(Win32API遵循stdcall传参规范的,不在本文讨论范围)
下面是测试代码
void Swap(__int64* _pnX, __int64* _pnY)
{
__int64 nTemp = *_pnX;
*_pnX = *_pnY;
*_pnY = nTemp;
} void Swap(__int64& _nX, __int64& _nY)
{
__int64 nTemp = _nX;
_nX = _nY;
_nY = nTemp;
} void SetValue(__int64 _nX)
{
__int64 nTemp = _nX;
} // Test001
void GetMemory(__int64* _pBuff)
{
_pBuff = new __int64[];
} // Test002
void GetMemory(__int64** _ppBuff)
{
*_ppBuff = new __int64[];
} int _tmain(int argc, _TCHAR* argv[])
{
__int64 nA = 0x10;
__int64 nB = 0x20; // Test to pass by pointer
Swap(&nA, &nB); // Test to pass by reference
Swap(nA, nB); // Test to pass by value
SetValue(nA); // Test the pointer that points the pointer
__int64* _pArray = NULL;
GetMemory(&_pArray);
delete[] _pArray;
_pArray = NULL; // Test the pointer
GetMemory(_pArray); return ;
}
指针传递和引用传递
// 下面看一下对应的反汇编的代码(VS版)
__int64 nA = 0x10;
0041370E mov dword ptr [nA],10h
mov dword ptr [ebp-],
__int64 nB = 0x20;
0041371C mov dword ptr [nB],20h
mov dword ptr [ebp-18h], // Test to pass by pointer
Swap(&nA, &nB);
0041372A lea eax,[nB]
0041372D push eax
0041372E lea ecx,[nA]
push ecx
call Swap (4111E5h)
add esp, // Test to pass by reference
Swap(nA, nB);
0041373A lea eax,[nB]
0041373D push eax
0041373E lea ecx,[nA]
push ecx
call Swap (4111E0h)
add esp, // GCC版
0x00401582 <+>: lea eax,[esp+0x18]
0x00401586 <+>: mov DWORD PTR [esp+0x4],eax
0x0040158a <+>: lea eax,[esp+0x1c]
0x0040158e <+>: mov DWORD PTR [esp],eax
0x00401591 <+>: call 0x401520 <Swap(int*, int*)>
0x00401596 <+>: lea eax,[esp+0x18]
0x0040159a <+>: mov DWORD PTR [esp+0x4],eax
0x0040159e <+>: lea eax,[esp+0x1c]
0x004015a2 <+>: mov DWORD PTR [esp],eax
0x004015a5 <+>: call 0x401542 <Swap(int&, int&)>
通过上面的反汇编代码,我们可以看出指针传递和引用传递在机制是一样的,都是将指针值(即地址)压入栈中,调用函数,然后恢复栈。Swap(nA, nB)和Swap(&nA, &nB);在实际上的汇编代码也基本上一模一样,都是从栈中取出地址来。由此可以看出引用和指针在效率上是一样的。这也是为什么指针和引用都可以达到多态的效果。指针传递和引用传递其实都是改变的地址指向的内存上的值来达到修改参数的效果。
值传递
下面是值传递对应的反汇编代码
// Test to pass by value
SetValue(nA);
0041374A mov eax,dword ptr [ebp-8]
0041374D push eax
0041374E mov ecx,dword ptr [nA]
00413751 push ecx
00413752 call SetValue (4111EAh)
00413757 add esp,8
因为我的机器是32位的CPU,从上面的汇编代码可以看64Bit的变量被分成2个32Bit的参数压入栈中。这也是我们常说的,值传递会形成一个拷贝。如果是一个自定义的结构类型,并且有很多参数,那么如果用值传递,这个结构体将被分割为非常多个32Bit的逐个拷贝到栈中去,这样的参数传递效率是非常慢的。所以结构体等自定义类型,都使用引用传递,如果不希望别人修改结构体变量,可以加上const修饰,如(const MY_STRUCT& _value);
下面来看一下Test001函数对应的反汇编代码的参数传递
__int64* _pArray = NULL;
004137E0 mov dword ptr [_pArray],
// Test the pointer
GetMemory(_pArray);
mov eax,dword ptr [_pArray]
push eax
call GetMemory (411203h)
0041381B add esp,
从上面的汇编代码可以看出,其实是0被压入到栈中作为参数,所以GetMemory(_pArray)无论做什么事,其实都与指针变量_pArray无关。GetMemory()分配的空间是让栈中的临时变量指向的,当函数退出时,栈得到恢复,结果申请的空间没有人管,就产生内存泄露的问题了。《C++ Primer》将参数传递分为引用传递和非引用传递两种,非引用传递其实可以理解为值传递。这样看来,指针传递在某种意义上也是值传递,因为传递的是指针的值(1个4BYTE的值)。值传递都不会改变传入实参的值的。而且普通的指针传递其实是改变的指针变量指向的内容。
下面再看一下Test002函数对应的反汇编代码的参数传递
__int64* _pArray = NULL;
004137E0 mov dword ptr [_pArray],
GetMemory(&_pArray);
004137E7 lea eax,[_pArray]
004137EA push eax
004137EB call GetMemory (4111FEh) 004137F0 add esp,
从上面的汇编代码lea eax,[_pArray] 可以看出,_pArray的地址被压入到栈中去了。
然后看一看GetMemory(&_pArray)的实现汇编代码。
0x0040159b <+0>: push ebp
0x0040159c <+1>: mov ebp,esp
0x0040159e <+3>: sub esp,0x18
0x004015a1 <+6>: mov DWORD PTR [esp],0x20
0x004015a8 <+13>: call 0x473ef0 <_Znaj>
0x004015ad <+18>: mov edx,DWORD PTR [ebp+0x8]
0x004015b0 <+21>: mov DWORD PTR [edx],eax
0x004015b2 <+23>: leave
0x004015b3 <+24>: ret
蓝色的代码是分配临时变量空间,然后调用分配空间函数分配空间,得到的空间指针即eax.
然后红色的汇编代码即从ebp+0x8的栈上取到上面压入栈中的参数_pArray的地址.
mov DWORD PTR [edx],eax即相当于把分配的空间指针eax让edx指向,也即让_pArray指向分配的空间eax.
总之,无论是哪种参数传递方式,参数都是通过栈上的临时变量来间接参与到被调用函数的。指针作为参数,其本身的值是不可能被改变的,能够改变的是其指向的内容。引用是通过指针来实现的,所以引用和指针在效率上一样的。
C/C++的参数传递机制的更多相关文章
- 深入剖析C/C++函数的参数传递机制
2014-07-29 20:16 深入剖析C/C++函数的参数传递机制 C语言的函数入口参数,可以使用值传递和指针传递方式,C++又多了引用(reference)传递方式.引用传递方式在使用上类 ...
- Python 函数参数传递机制.
learning python,5e中讲到.Python的函数参数传递机制是对象引用. Arguments are passed by assignment (object reference). I ...
- python中的*和**参数传递机制
python的参数传递机制具有值传递(int.float等值数据类型)和引用传递(以字典.列表等非值对象数据类型为代表)两种基本机制以及方便的关键字传递特性(直接使用函数的形参名指定实参的传递目标,如 ...
- 我的Java开发学习之旅------>Java语言中方法的参数传递机制
实参:如果声明方法时包含来了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时传给形参的参数值也被称为实参. Java的实参值是如何传入方法?这是由Java方法的参数传递机制来控制的,Java ...
- 深入理解Java中方法的参数传递机制
形参和实参 我们知道,在Java中定义方法时,是可以定义参数的,比如: public static void main(String[] args){ } 这里的args就是一个字符串数组类型的参数. ...
- Java方法之参数传递机制
目录 Java方法之参数传递机制 基本数据类型 引用数据类型 综合练习 总结 Java方法之参数传递机制 Java方法中如果声明了形参,在调用方法时就必须给这些形参指定参数值,实际传进去的这个值就叫做 ...
- JavaSE 面试题: 方法的参数传递机制
JavaSE 面试题 方法的参数传递机制 import java.util.Arrays; public class Test { public static void main(String[] a ...
- Java高频经典面试题(第一季)四:方法的参数传递机制
考点? 方法的参数传递机制 String,包装类等对象的不可变性 方法的参数传递机制: ①形参是基本数据类型 传递数据值 ②实参是引用数据类型 传递地址值 特殊的类型:String.包装类等对象不可变 ...
- 浅谈Java参数传递机制
Java参数传递 才疏学浅,今天才知道Java中方法的参数是可以传递对象引用进去的. Java的参数传递机制很简单,其实就是值传递. 所谓值传递,也就是我们在给方法传递一个参数的时,传递的 ...
- C++函数的参数传递机制以及参数的类型选择
C++primer之函数的参数传递以及参数的类型 一:函数的基本知识 (1) 函数要素:返回类型,函数名字,形参(参数之间用逗号隔开) (2) 函数调用机制:我们通过调用运算符来执 ...
随机推荐
- 第二百零八天 how can I 坚持
今天徐斌生日,生日快乐.买了两个小蛋糕,哈哈 还买了两条熊猫鱼.不知道鱼会不会冻死啊,买了加热器又不想用,看他们造化吧. LOL不错的游戏的. 睡觉,好冷.
- 转】Linux下安装Tomcat服务器和部署Web应用
原博文出自于: http://www.cnblogs.com/xdp-gacl/p/4097608.html 感谢! 一.上传Tomcat服务器
- XSS攻击及防御(转)
add by zhj: 略有修改.另外还有一篇文章值得参考,使用 PHP 构建的 Web 应用如何避免 XSS 攻击,总得来说防御XSS的方法是客户端和服务端都 要对输入做检查,如果只有客户端做检查, ...
- Styling FX Buttons with CSS
http://fxexperience.com/2011/12/styling-fx-buttons-with-css/ ——————————————————————————————————————— ...
- HD1085 Holding Bin-Laden Captive!
Problem Description We all know that Bin-Laden is a notorious terrorist, and he has disappeared for ...
- Physicals
[Physicals] The physics simulation in Sprite Kit is performed by adding physics bodies to scenes. [T ...
- Wicket Hello World Example
A simple hello world example in Wicket, show the basic structure of Wicket web application. Tools an ...
- usb库文件usb_desc.c分析
参考<圈圈教你玩USB> usb协议中使用的是小端结构,所以实际数据在传输时是低字节在先的. 设备描述符的实现: 已知每个设备都必须有且仅有一个设备描述符,它的结构在USB协议中有详细的定 ...
- PyQt多窗口调用
经常有人问到如何在一个主窗口中打开一个对话框,然后在确认对话框之后,开启另一个窗口进行后续操作,要求主窗口和最终的窗口之间都能响应用户操作,也就是非模态窗口.随手写了几行代码,简要示意. :::pyt ...
- 如何防止ASP.NET网站遭受CSRF的攻击
转载地址: http://www.cnblogs.com/shanyou/p/5038794.html?hmsr=toutiao.io&utm_medium=toutiao.io&ut ...