c++ 引用底層實現

紅色是我添加的,其餘地方是原做者的。ios

主要是看了上面的這篇「從底層彙編理解 c++ 引用實現機制「的文章以後,以爲不錯。就轉了過來,同時,對文中的程序都在本身的機器上驗證了一下。c++

使用的G++版本:g++ (GCC) 4.5.1 20100924程序員

若是要查看彙編後代碼與源碼的關係,我用的方法是:面試

先用g++生成帶有調試信息的目標文件:g++ -g -c ref.cc函數

而後再利用objdump命令查看目標文件ref.o:objdump -S ref.ospa

引用類型究竟是什麼?它和指針有什麼關係?它自己佔用內存空間嗎? 帶着這些疑問,咱們來進行分析。 先看代碼:.net

  1. #include <stdio.h>  3d

  2. #include <iostream>  指針

  3.   

  4. using namespace std;  調試

  5.   

  6.   

  7. void main()  

  8. {  

  9.    int x = 1;  

  10.    int &b = x;  

  11.  }   

int main()

{

int x=1;

int &b=x;

return 0;

}

  經過彙編查看代碼以下:



  1. 9:       int x = 1;  

  2. 00401048   mov         dword ptr [ebp-4],1  

  3. 10:      int &b = x;  

  4. 0040104F   lea         eax,[ebp-4]  

  5. 00401052   mov         dword ptr [ebp-8],eax  

00000000 <main>:

int main()

{

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 ec 10 sub $0x10,%esp

int x=1;

6: c7 45 f8 01 00 00 00 movl $0x1,-0x8(%ebp)

int &b=x;

d: 8d 45 f8 lea -0x8(%ebp),%eax

10: 89 45 fc mov %eax,-0x4(%ebp)

return 0;

13: b8 00 00 00 00 mov $0x0,%eax

}

18: c9 leave

19: c3 ret



  能夠知道x的地址爲ebp-4,b的地址爲ebp-8,由於棧內的變量內存是從高往低進行分配的。因此b的地址比x的低。
  lea eax,[ebp-4]  這條語句將x的地址ebp-4放入eax寄存器

  mov dword ptr [ebp-8],eax 這條語句將eax的值放入b的地址ebp-8中

  上面兩條彙編的做用即:將x的地址存入變量b中,這不和將某個變量的地址存入指針變量是同樣的嗎?

   因此從彙編層次來看,的確引用是經過指針來實現的。

   下面咱們經過程序來驗證,咱們知道,在程序一層咱們只要直接涉及到引用變量的操做,咱們操做的老是被引用變量,即編譯器幫咱們作了些手腳,老是在引用前面加上*。因此咱們要讀取真正的「引用變量的值」,必須採起必定的策略,好吧,咱們就按照變量在棧中分佈的特色來繞過編譯器的這個特色。

[cpp] view plaincopyprint?

  1. #include <stdio.h>  

  2. #include <iostream>  

  3.   

  4. using namespace std;  

  5.   

  6. void main()  

  7. {  

  8.    int x = 1;  

  9.    int y = 2;  

  10.    int &b = x;  

  11.    printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));  

  12.  }   

  輸出結果爲:&x=12ff7c,&y=12ff78,&b=12ff74,b=12ff7c

#include <cstdio>

 

int main()

{

int x=1;

int y=2;

int &b=x;

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

return 0;

}

輸出結果是:

&x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460

 

這個地方的結果和做者不同,能夠看後面的解釋。





  1. void main()  

  2. {  

  3.    int x = 1;  

  4.    int &b = x;  

  5.    printf("&x=%x,&b=%x\n",&x,&b);  

  6.  }   

  輸出結果爲:&x=12ff7c,&b=12ff7c.

#include <cstdio>

 

int main()

{

int x=1;

int &b=x;

printf("&x=%x,&b=%x\n",&x,&b);

return 0;

}

輸出結果:&x=bfe74aa8,&b=bfe74aa8



  b的地址咱們無法經過&b得到,由於編譯器會將&b解釋爲:&(*b) =&x ,因此&b將獲得&x。也驗證了對全部的b的操做,和對x的操做等同。

  可是咱們能夠間接經過&y-1來獲得b的地址,從而獲得b的值:*(&y-1)  從結果能夠知道,b的值即x的地址,從而能夠知道,從地層實現來看,引用變量的確存放的是被引用對象的地址,只不過,對於高級程序員來講是透明的,編譯器 屏蔽了引用和指針的差異。

  下面是程序的變量在內存棧中的分佈,引用變量同樣也佔用內存空間,並且應該是4個字節的空間。

   雖然從底層來講,引用的實質是指針,可是從高層語言級別來看,咱們不能說引用就是指針,他們是兩個徹底不一樣的概念。有人說引用是受限的指針,這種說法我不贊同,由於從語言級別上,指針和引用沒有關係,引用就是另外一個變量的別名。對引用的任何操做等價於對被引用變量的操做。從語言級別上,咱們就不要去考慮它的底層實現機制啦,由於這些對你是透明的。因此在面試的時候,若是面試的人問到這個問題,能夠先從語言級別上談談引用,深刻的話就從底層的實現機制進行分析。而不能什麼條件沒有就說:引用就是指針,沒有差異,......之類的回答

 

對於下面的程序:

#include <cstdio>

 

int main()

{

int x=1;

int y=2;

int &b=x;

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

return 0;

}

個人結果是&x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460

與原做者的不一樣,而後我就將上面的程序進行編譯獲得下面的結果:

00000000 <main>:

#include <cstdio>

 

int main()

{

0: 55 push %ebp

1: 89 e5 mov %esp,%ebp

3: 83 e4 f0 and $0xfffffff0,%esp

6: 83 ec 30 sub $0x30,%esp

int x=1;

9: c7 44 24 28 01 00 00 movl $0x1,0x28(%esp) 1賦值給x(x在堆棧的0x28處)

10: 00

int y=2;

11: c7 44 24 24 02 00 00 movl $0x2,0x24(%esp) 2賦值給y(y在堆棧的0x24處)

18: 00

int &b=x;

19: 8d 44 24 28 lea 0x28(%esp),%eax x的地址0x28 傳給寄存器%eax

1d: 89 44 24 2c mov %eax,0x2c(%esp) %eax的值賦給堆棧0x2c處(這兒比較重要)

printf("&x=%x,&y=%x,&b=%x,b=%x\n",&x,&y,&y-1,*(&y-1));

21: 8d 44 24 24 lea 0x24(%esp),%eax 將堆棧0x24處的地址傳給寄存器%eax

25: 83 e8 04 sub $0x4,%eax %eax的值減掉4

28: 8b 10 mov (%eax),%edx 將寄存器%eax中地址所指向的內容傳給寄存器%edx

2a: 8d 44 24 24 lea 0x24(%esp),%eax 將堆棧0x24處的地址傳給寄存器%eax

2e: 83 e8 04 sub $0x4,%eax %eax的值減掉4

31: 89 54 24 10 mov %edx,0x10(%esp) %edx的內容傳給堆棧0x10處

35: 89 44 24 0c mov %eax,0xc(%esp) %eax的內容傳給堆棧0xc處

39: 8d 44 24 24 lea 0x24(%esp),%eax 將堆棧0x24處的地址傳給寄存器%eax

3d: 89 44 24 08 mov %eax,0x8(%esp) %eax的內容傳給堆棧0x8處

41: 8d 44 24 28 lea 0x28(%esp),%eax 將堆棧0x28處的地址傳給寄存器%eax

45: 89 44 24 04 mov %eax,0x4(%esp) %eax的內容傳給堆棧0x4處

49: c7 04 24 00 00 00 00 movl $0x0,(%esp)

50: e8 fc ff ff ff call 51 <main+0x51>

return 0;

55: b8 00 00 00 00 mov $0x0,%eax

}

5a: c9 leave

5b: c3 ret

 

上面基本每一句中都進行解釋。

從這兒能夠看到個人機器上生成的彙編代碼是將x的地址賦給了堆棧中地址所在處的下一個地址單元。

從printf所生成的彙編代碼處咱們也能夠看到,是按照逆序來計算的(&*(&y-1), &y-1, &y, x)這也印證了C標準中提到的函數的參數是逆序入棧的。

爲了驗證上面的想法,將原程序中的*(&y-1)改成*(&x+1)

結果爲:

&x=bf9a74c8,&y=bf9a74c4,&b=bf9a74cc,b=bf9a74c8

這樣就和做者的符合起來了。

相關文章
相關標籤/搜索