关于C++引用的一个探索

来源:互联网 发布:数码暴龙网络侦探多大 编辑:程序博客网 时间:2024/05/17 19:17

 

一直以来对引用这个东西都有疑惑,今天决心要把它弄清楚.

环境:VC6.0 + XP SP2

 

 

实验一:

VC6.0编译如下代码:

void c(int &b)

{

       b+=2;

}

void main()

{

       int a=5;

       cout<<a<<endl;

       int &b=a;

       cin>>b;

       c(b);

       cin>>b;

}

然后将生成的程序反汇编,得到如下代码:

00401010  /$  51           PUSH ECX                                 00401011  |.  6A 05         PUSH 5                 /Arg1 = 00000005

00401013  |.  B9 40CA4000   MOV ECX,cri.0040CA40                     ; |

00401018  |.  C74424 04 050>MOV DWORD PTR SS:[ESP+4],5  ;这里就是int a=5;此时ESP=0013FF7C

00401020  |.  E8 2D020000   CALL cri.00401252                        ; /cri.00401252

00401025  |.  68 90104000   PUSH cri.00401090

0040102A  |.  6A 0A         PUSH 0A                                  ; /Arg1 = 0000000A

0040102C  |.  8BC8          MOV ECX,EAX                              ; |

0040102E  |.  E8 81010000   CALL cri.004011B4                        ; /cri.004011B4

00401033  |.  8BC8          MOV ECX,EAX

00401035  |.  E8 36000000   CALL cri.00401070     ;这里就是cout<<a<<endl;

0040103A  |.  8D4424 00     LEA EAX,DWORD PTR SS:[ESP]  ;ESP=0013FF80,这里意思是把a的地址放到寄存器EAX

0040103E  |.  B9 F0C94000   MOV ECX,cri.0040C9F0

00401043  |.  50            PUSH EAX  ;a的地址压栈,或者说把指向a的指针压栈,这时堆栈中0013FF7C保存的就是指向a的指针(0013FF80)

00401044  |.  E8 57000000   CALL cri.004010A0   ;跟进这里

 

接着看到如下代码:

004010A0  /$  55            PUSH EBP

004010A1  |.  8BEC          MOV EBP,ESP

004010A3  |.  83EC 10       SUB ESP,10

004010A6  |.  56            PUSH ESI

004010A7  |.  8BF1          MOV ESI,ECX

004010A9  |.  6A 00         PUSH 0

004010AB  |.  E8 06090000   CALL cri.004019B6  ;这里就是第一个cin>>b;这里我输入”90”,程序以ASCII码保存在0013FF64=00003039

004010B0  |.  85C0          TEST EAX,EAX

004010B2  |.  74 46         JE SHORT cri.004010FA

004010B4  |.  8D45 F0       LEA EAX,DWORD PTR SS:[EBP-10]

004010B7  |.  8BCE          MOV ECX,ESI

004010B9  |.  50            PUSH EAX                                 ; /Arg1

004010BA  |.  E8 95060000   CALL cri.00401754                        ; /cri.00401754

004010BF  |.  50            PUSH EAX

004010C0  |.  8D45 F0       LEA EAX,DWORD PTR SS:[EBP-10]

004010C3  |.  6A 00         PUSH 0

004010C5  |.  50            PUSH EAX  ;把字符串”90”压栈

004010C6  |.  E8 3A120000   CALL cri.00402305  ;这里就是把”90”这个字符串转成整数90,相当于函数atoi()的功能,这个call过之后EAX就等于十六进制5A

004010CB  |.  B9 FFFFFF7F   MOV ECX,7FFFFFFF

004010D0  |.  83C4 0C       ADD ESP,0C

004010D3  |.  3BC1          CMP EAX,ECX

004010D5  |.  7E 15         JLE SHORT cri.004010EC

004010D7  |>  8B45 08       MOV EAX,DWORD PTR SS:[EBP+8]

004010DA  |.  8908          MOV DWORD PTR DS:[EAX],ECX

004010DC  |.  8B06          MOV EAX,DWORD PTR DS:[ESI]

004010DE  |.  8B40 04       MOV EAX,DWORD PTR DS:[EAX+4]

004010E1  |.  834C30 08 02  OR DWORD PTR DS:[EAX+ESI+8],2

004010E6  |.  8D4430 08     LEA EAX,DWORD PTR DS:[EAX+ESI+8]

004010EA  |.  EB 0E         JMP SHORT cri.004010FA

004010EC  |>  B9 00000080   MOV ECX,80000000

004010F1  |.  3BC1          CMP EAX,ECX

004010F3  |.^ 7C E2         JL SHORT cri.004010D7

004010F5  |.  8B4D 08       MOV ECX,DWORD PTR SS:[EBP+8]  ;此时EBP+8=0013FF7C,这里保存的就是指向a的指针,意思就是把0013FF7C内存单元中的值(0013FF80)放到ECX

004010F8  |.  8901          MOV DWORD PTR DS:[ECX],EAX  ;把十进制数90放到地址为0013FF80的内存单元中,从这里可以看出cin>>b;本质上就是cin>>a;

004010FA  |>  8BC6          MOV EAX,ESI

004010FC  |.  5E            POP ESI

004010FD  |.  C9            LEAVE

004010FE  /.  C2 0400       RETN 4  ;函数返回

 

出来后就到这里:

00401049  |.  8D4C24 00     LEA ECX,DWORD PTR SS:[ESP]

0040104D  |.  51            PUSH ECX  ;这里ECX=0013FF80,也就是调用c(int &b)函数时传了个指向a的指针进去

0040104E  |.  E8 ADFFFFFF   CALL cri.00401000  ;这里就是c(b);跟进去

 

看到如下代码:

00401000  /$  8B4424 04     MOV EAX,DWORD PTR SS:[ESP+4]  ;此时ESR+4=0013FF7C,意思是把0013FF80放到EAX,也就是把a的值放到EAX

00401004  |.  8300 02       ADD DWORD PTR DS:[EAX],2  ;这里就是b+=2;

00401007  /.  C3            RETN

通过上面代码可以发现,传进的参数b根本没用到,而且这个时候b被解析成了指向a的指针

 

 

 

00401053  |.  83C4 04       ADD ESP,4

00401056  |.  8D5424 00     LEA EDX,DWORD PTR SS:[ESP]

0040105A  |.  B9 F0C94000   MOV ECX,cri.0040C9F0

0040105F  |.  52            PUSH EDX  ;这里EDX=0013FF80

00401060  |.  E8 3B000000   CALL cri.004010A0  ;这里就是第二个cin>>b;其实看压栈的参数可以知道这里的b直接就被编译解析成a,等价于cin>>a;

00401065  |.  59            POP ECX

00401066  /.  C3            RETN  ;出去程序就结束了

 

实验二:

接着把代码改一下,改后如下:

void main()

{

       int a=5;

       cout<<a<<endl;

       int &b=a;

       cin>>b;

       cin>>b;

}

没有函数定义和调用了,再次反汇编,得到的只是比上面少了函数调用和定义,其它都一样,而且在0013FF7C这个内存单元还是保存着0013FF80,0013FF80内存单元保存着a的值

 

实验三:

在改一下上面刚改过的代码,改成如下:

void main()

{

       int a=5;

       cout<<a<<endl;

       int &b=a;

       int &c=a;

       cin>>b;

       cin>>b;

}

再次编译,结果和第二次编译的完全一样

 

通过上面的实验,可以得到这样的结论:

同一个函数里面的引用在用的时候都是直接用被引用的数,它的别名根本没用到,但是编译器还是为别名分配了内存,而且别名都被编译成指针了,当函数的参数是引用某个数的时候,其实传进去的就是一个指向被引用的变量的指针.

例如void func(int &b);就最终编译的时候变成void func(int *b);

 

还有,实验三证明了定义多个引用(我这里三个,四个或更多的引用没帖上来,结果都是一样的)编译器都只会分配一个内存给这些别名.

 

(:我这里讲编译器为***分配内存是迫于不知道怎么表达,估计大家会理解我也能理解我.)

 

我个人感觉引用其实只是很概念的东西,虽说是别名,同一个变量的引用还是同一个指向它的指针,和多个指针一样,只是在概念上和编写代码上给我们提供和指针不同的方式,有时也会带来一些方便,或许也是从概念上改变指针(毕竟指针被认为是万恶之源),让大家编写代码的时候不为指针的边界操心,就像JAVAC#说的一样,”没有指针了”,其实这句话是说大家不用担心指针带来的边界问题了,这个由编译器自行解决”.以上属于个人理解,我还小,估计这样的理解有很多错误,错误之处请大家纠正.