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[4];
}

// Test002
void GetMemory(__int64** _ppBuff)
{
    *_ppBuff = new __int64[4];
}

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 0;
}

指针传递和引用传递

// 下面看一下对应的反汇编的代码(VS版)
__int64 nA = 0x10;
0041370E  mov        dword ptr [nA],10h
 mov        dword ptr [ebp-8],0
__int64 nB = 0x20;
0041371C  mov        dword ptr [nB],20h
 mov        dword ptr [ebp-18h],0

// 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,8

// 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,8

// GCC版
  0x00401582 <+30>:    lea    eax,[esp+0x18]
  0x00401586 <+34>:    mov    DWORD PTR [esp+0x4],eax
  0x0040158a <+38>:    lea    eax,[esp+0x1c]
  0x0040158e <+42>:    mov    DWORD PTR [esp],eax
  0x00401591 <+45>:    call  0x401520 <Swap(int*, int*)>
  0x00401596 <+50>:    lea    eax,[esp+0x18]
  0x0040159a <+54>:    mov    DWORD PTR [esp+0x4],eax
  0x0040159e <+58>:    lea    eax,[esp+0x1c]
  0x004015a2 <+62>:    mov    DWORD PTR [esp],eax
  0x004015a5 <+65>:    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

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/4ce313a224e66c90c0eeb30afcc20f7e.html