黄伟文垃圾五部曲:深入C\C++的引用,看看编译器对我们隐瞒了什么!(本实验在vs2008中进行)

来源:百度文库 编辑:九乡新闻网 时间:2024/04/27 02:19:27
 许多教材书上都说,C++的引用,就是给变量取别名,即他们是同一个内存地址的两个名字。我觉得玄乎,就决定自己去看个究竟。
       既然引用和变量名称是内存地址的两个名字,最直接的想法就是把他们的内存地址打印出来看看,OK,代码实验:
#include
using namespace std;
int main()
{
    int i = 0;
    int &j = i;
    i = 1;
    j = 1;
    cout<<&i<<","<<&j<}
输出的结果为:0017FC04,0017FC04
       果然,他们的的地址是同一个。
       也许你会觉得真相已经大白,但这真的是真相吗?那么,让我们对这段代码进行反编译看看吧!为了与指针区别,我们将源代码更改如下:
int main()
{
     int i = 0;
     int &j = i;
     int *p = &i;
     i = 1;
     j = 1;
     *p = 1;
     cout<<&i<<","<<&j<<","<<&p<}
对其进行反编译:
int main()
{
00DE3350  push        ebp  
00DE3351  mov         ebp,esp
00DE3353  sub         esp,0E8h
00DE3359  push        ebx  
00DE335A  push        esi  
00DE335B  push        edi  
00DE335C  lea         edi,[ebp-0E8h]
00DE3362  mov         ecx,3Ah
00DE3367  mov         eax,0CCCCCCCCh
00DE336C  rep stos    dword ptr es:[edi]
int i = 0;
00DE336E  mov         dword ptr ,0
int &j = i;
00DE3375  lea         eax,
00DE3378  mov         dword ptr [j],eax
int *p = &i;
00DE337B  lea         eax,
00DE337E  mov         dword ptr [p],eax
i = 1;
00DE3381  mov         dword ptr ,1
j = 1;
00DE3388  mov         eax,dword ptr [j]
00DE338B  mov         dword ptr [eax],1
*p = 1;
00DE3391  mov         eax,dword ptr [p]
00DE3394  mov         dword ptr [eax],1
cout<<&i<<","<<&j<<","<<&p<00DE339A  mov         esi,esp
00DE339C  mov         eax,dword ptr [__imp_std::endl (0DEC390h)]
00DE33A1  push        eax  
00DE33A2  mov         edi,esp
00DE33A4  lea         ecx,[p]
00DE33A7  push        ecx  
00DE33A8  push        offset string "," (0DE8768h)
00DE33AD  mov         ebx,esp
00DE33AF  mov         edx,dword ptr [j]
00DE33B2  push        edx  
00DE33B3  push        offset string "," (0DE8768h)
00DE33B8  mov         eax,esp
00DE33BA  lea         ecx,
00DE33BD  push        ecx  
00DE33BE  mov         ecx,dword ptr [__imp_std::cout (0DEC394h)]
00DE33C4  mov         dword ptr [ebp-0E8h],eax
00DE33CA  call        dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE33D0  mov         ecx,dword ptr [ebp-0E8h]
00DE33D6  cmp         ecx,esp
00DE33D8  call        @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33DD  push        eax  
00DE33DE  call        std:: operator<< > (0DE1339h)
00DE33E3  add         esp,8
00DE33E6  mov         ecx,eax
00DE33E8  call        dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE33EE  cmp         ebx,esp
00DE33F0  call        @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE33F5  push        eax  
00DE33F6  call        std:: operator<< > (0DE1339h)
00DE33FB  add         esp,8
00DE33FE  mov         ecx,eax
00DE3400  call        dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC398h)]
00DE3406  cmp         edi,esp
00DE3408  call        @ILT+585(__RTC_CheckEsp) (0DE124Eh)
00DE340D  mov         ecx,eax
00DE340F  call        dword ptr [__imp_std::basic_ostream >:: operator<< (0DEC39Ch)]
00DE3415  cmp         esi,esp
00DE3417  call        @ILT+585(__RTC_CheckEsp) (0DE124Eh)
}
       我了个去,好长的汇编代码,不过有汇编基础的人看懂它必然是不在话下,下面我们就来分析它,首先是初始化代码
int i = 0;
00DE336E  mov         dword ptr ,0
int &j = i;
00DE3375  lea         eax,
00DE3378  mov         dword ptr [j],eax
int *p = &i;
00DE337B  lea         eax,
00DE337E  mov         dword ptr [p],eax
      乍一看,怎么引用和指针的初始化一模一样!都是将i变量单元的地址赋给它们!再看他们的赋值表达式:
j = 1;
00DE3388  mov         eax,dword ptr [j]
00DE338B  mov         dword ptr [eax],1
*p = 1;
00DE3391  mov         eax,dword ptr [p]
00DE3394  mov         dword ptr [eax],1
原来引用就是不加*直接访问的是其保存的地址处的值,而指针需要加*才访问其保存的地址处的值,这一定程度上方便了我们的使用。
还有一个问题要我们解决:我们明明在第一个实验的时候,输出引用的地址和变量的地址是一样的,可是反汇编代码里,我们发现,他们占用的是不同的内存单元!这倒底是怎么回事?
      带着问题我们继续往下看:
cout<<&i<<","<<&j<<","<<&p<00DE339A  mov         esi,esp
00DE339C  mov         eax,dword ptr [__imp_std:: endl (0DEC390h)]
00DE33A1  push        eax  
00DE33A2  mov         edi,esp
00DE33A4  lea         ecx,[p]
00DE33A7  push        ecx  
00DE33A8  push        offset string "," (0DE8768h)
00DE33AD  mov         ebx,esp
00DE33AF  mov         edx,dword ptr [j]
00DE33B2  push        edx  
00DE33B3  push        offset string "," (0DE8768h)
00DE33B8  mov         eax,esp
00DE33BA  lea         ecx,
00DE33BD  push        ecx  
以上代码是输出语句的一部分,首先看
&p对应的汇编为
00DE33A4  lea         ecx,[p]
这两句代码的意思是指得到p的偏移地址,同理
&i对应的汇编
00DE33BA  lea         ecx,
是将i的偏移地址推入栈,接下来是最关键的代码&j对应的汇编:
00DE33AF  mov         edx,dword ptr [j]
竟然和之前的汇编代码不一样!我们来看看他作了什么,原来他并没有获取j单元的内存地址,而是直接将j单元的值给移到了edx中,而j单元存的是i内存单元的地址,怪不得我们的代码&j得到的是i的地址,原来是这么一回事。
好了,我们来总结一下:
从表面上看,引用就是变量的别名而已,引用与变量仅仅是同一个内存单元的两个名字,就像一个人会有中文名与英文名,但都代码同一个人。
从底层的实现上看,引用其实存的是变量的地址,这些和指针一样,只是经过一系统的处理,让我们从宏观角度上看去,就是变量的一个别名。

注:以上数据仅仅是我在vs2008的运行结果,其它编译器的编译也许会存在差异,本人未曾试验。