C++语言中引用机制的实现分析
原文地址:http://blog.sina.com.cn/s/blog_6fd68d5f0100n5zy.html
一案例代码(VS2005 C++环境下调试)
#include <stdio.h>
int& RefFun(int& n)//通过引用传递参数
{
n++;
return n;
}
int ValueFun(int n)//通过变量值传递参数
{
n++;
return n;
}
int main(int argc,char* argv[])
{
int a=10;
int& x=a;//引用变量初始化
int* pInt=&a;//取普通变量的地址
int* pref=&x;//取引用变量的地址(其实获取的并不是引用变量的地址,而是被引用变量a的地址)
int& b=RefFun(a);//函数返回引用,并将引用的别名赋值给引用变量
int c=RefFun(a);//函数返回引用,并将引用变量的值赋值给整数变量
int d=ValueFun(a);//函数返回变量值,
printf("a=%d,b=%d,c=%d\n",a,b,c);
return 0;
}
二 对上面的Main函数反汇编代码分析
注:
1)以下是在VS2005下,使用C++工程进行调试(切换到反汇编模式)显示的代码;
2)每行源代码下面为其对应的汇编代码;
3)读者也可以在自己的VS2005开发平台下进行反汇编分析!
反汇编分析代码如下:
int main(int argc,char* argv[])
{
00411430 push ebp
00411431 mov ebp,esp
00411433 sub esp,114h
00411439 push ebx
0041143A push esi
0041143B push edi
0041143C lea edi,[ebp-114h]
00411442 mov ecx,45h
00411447 mov eax,0CCCCCCCCh
0041144C rep stos dword ptr es:[edi]
int a=10;
//对应的汇编代码:立即数寻址,给变量a赋值10,注意:dwordptr[a]为变量a的地址
0041144E mov dword ptr [a],0Ah
int& x=a;//引用变量初始化:其实质就是保存变量a的地址值到引用变量的内存单元!
00411455 lea eax,[a]
00411458 mov dword ptr [x],eax
int* pInt=&a;//取普通变量的地址
0041145B lea eax,[a] //[a]表示变量a的地址;
0041145E mov dword ptr [pInt],eax //变量a的地址保存到指针变量pInt中:这也是指针变量的原理;
//下面的代码取引用变量的地址值,在实际很少用到该方式,在这里仅用于案例说明
int* pref=&x;//取引用变量的地址(其实获取的并不是引用变量的地址,而是被引用变量a的地址)
00411461 mov eax,dword ptr [x] //取引用变量内存单元保存的值(不是引用变量地址)
00411464 mov dword ptr [pref],eax //引用变量内存单元值保存到指针变量;
//对上面两行源代码的汇编分析补充:
//1)[x]表示引用变量x内存单元地址,dwordptr[x]:表示内存单元X保存的值(实际是一个地址值,实际指向被引用的变量a的值);
//2)引用变量的实现秘密:引用变量在内部实现其实就是一个常量指针变量;
//3)分析上面取普通变量地址,与取引用的地址内部实现机理是不一样的;
int& b=RefFun(a);//函数返回引用,并将引用的别名赋值给引用变量
00411467 lea eax,[a] //取变量a地址到EAX
0041146A push eax //变量a的地址为:0x411159h
0041146B call RefFun (411159h) //引用传递变量地址(指针)--引用实现的内部秘密!
00411470 add esp,4
00411473 mov dword ptr [b],eax //EAX返回的为变量a的地址:实际为变量a的地址(初始化引用变量b)
int c=RefFun(a);//函数返回引用,并将引用变量的值赋值给整数变量
00411476 lea eax,[a]
00411479 push eax //变量a的地址入栈(传递给函数的引用参数实际是变量的地址)
0041147A call RefFun (411159h)
0041147F add esp,4
00411482 mov ecx,dword ptr [eax]
00411484 mov dword ptr [c],ecx
int d=ValueFun(a);//函数返回变量值,
00411487 mov eax,dword ptr [a] //取变量a的值送到EAX寄存器
0041148A push eax //变量a的值入栈(传递给函数:ValueFun);
0041148B call ValueFun (4110DCh)
00411490 add esp,4
00411493 mov dword ptr [d],eax
printf("a=%d,b=%d,c=%d\n",a,b,c);
00411496 mov esi,esp
00411498 mov eax,dword ptr [c]
0041149B push eax
0041149C mov ecx,dword ptr [b]
0041149F mov edx,dword ptr [ecx]
004114A1 push edx
004114A2 mov eax,dword ptr [a]
004114A5 push eax
004114A6 push offset string "a=%d,b=%d,c=%d\n" (41563Ch)
004114AB call dword ptr [__imp__printf (4182B8h)]
004114B1 add esp,10h
004114B4 cmp esi,esp
004114B6 call @ILT+305(__RTC_CheckEsp) (411136h)
return 0;
004114BB xor eax,eax
}
三代码实现分析---变量值传递参数&引用传递参数的区别
1对上面案例代码引用传递参数的RefFun分析--反汇编分析
int& RefFun(int& n)//通过引用传递参数
{
004113A0 push ebp
004113A1 mov ebp,esp
004113A3 sub esp,0C0h
004113A9 push ebx
004113AA push esi
004113AB push edi
004113AC lea edi,[ebp-0C0h]
004113B2 mov ecx,30h
004113B7 mov eax,0CCCCCCCCh
004113BC rep stos dword ptr es:[edi]
n++;//简单的一个加1对应5行汇编代码(下面的传值函数,只有3行,这是区别所在)
//下面为对应的汇编代码,注意与下面函数ValueFun通过变量值传递参数的方式区别:
004113BE mov eax,dword ptr [n] //eax保存传递过来引用变量的地址值
004113C1 mov ecx,dword ptr [eax] //取到传递过来的变量值
004113C3 add ecx,1
004113C6 mov edx,dword ptr [n] //引用变量的地址保存到EDX
004113C9 mov dword ptr [edx],ecx //保存加1后的结果;
return n;//由于变量n保存的为变量的地址,因此这里返回的是传递进来的地址值(区别于传值)
004113CB mov eax,dword ptr [n]
}
2对上面案例代码引用传递参数的ValueFun分析--反汇编分析
int ValueFun(int n)//通过变量值传递参数
{
004113F0 push ebp
004113F1 mov ebp,esp
004113F3 sub esp,0C0h
004113F9 push ebx
004113FA push esi
004113FB push edi
004113FC lea edi,[ebp-0C0h]
00411402 mov ecx,30h
00411407 mov eax,0CCCCCCCCh
0041140C rep stos dword ptr es:[edi]
n++;//对应的汇编代码(只有3行汇编代码,比上面的引用传递参数,相对简单):
0041140E mov eax,dword ptr [n] //dwordptr[n]为栈上的临时变量n的地址;
00411411 add eax,1
00411414 mov dword ptr [n],eax
return n;//对应的汇编代码:返回的是栈上变量的值(这一点区别于引用,引用返回地址)
00411417 mov eax,dword ptr [n]
}
四 引用的实现秘密
通过对上面二,三章代码的反汇编代码的实现分析,对于引用变量的内部实现,可以得出如下结论:1)引用的内部实现为相当于一个指针变量,与指针的实现方式类似;
2)引用变量内存单元保存的指向变量地址(初始化时赋值),与指针不同地方时,引用变量在定义时必须初始化,而且使用过程中,引用变量保存的内存单元地址值是不能改变的(这一点通过编译器来实现保证);
3)引用也可以进行取地址操作,但是取地址操作返回的不是引用变量所在的内存单元地址,而是被引用变量本身所在的内存单元地址;
4)引用的使用,在源代码级相当于普通的变量一样使用,但在函数参数传递引用变量时,内部传递的实际是变量的地址值(这种机制的实现是通过编译器(编译手段)来实现的)。