C++的那些事:你真的瞭解引用嗎

1、引用的本質是什麼

說到引用,通常C++的教材中都是這麼定義的:html

1,引用就是一個對象的別名。安全

2,引用不是值不佔內存空間。函數

3,引用必須在定義時賦值,將變量與引用綁定。工具

那你有沒有想過,上面的定義正確嗎?編譯器是如何解釋引用的?spa

這裏先給出引用的本質定義,後面咱們再進一步論證。設計

1,引用實際是經過指針實現的。指針

2,引用是一個常量指針。code

3,引用在內存中佔4個字節。orm

4,在對引用定義時,須要對這個常量指針初始化。htm

2、探究本質

咱們從最簡單的變量的定義開始,看編譯器會作哪些事情。

int var = 42;mov         dword ptr [var],2Ah  // 對應彙編代碼

上面語句申請了一塊內存空間,佔4個字節,存放了一個int型的變量。內存裏放的是42的二進制碼。

彙編代碼向咱們表達的意思就是把42寫入以var爲地址的內容區域。var有點像咱們理解上的指針,只是編譯器並無把它抽象出來,而是讓咱們更表象的理解:申請一個變量,它的值爲42。

那麼var這個變量名放在哪呢

咱們知道程序若是訪問內存裏的數據,須要經過地址來進行訪問,因此上面的代碼在通過編譯器生成目標代碼時,用存放42的地址了全部的var,因此結論時,目標文件中不存在var,因此變量名自己是不佔內存的

而咱們知道,引用是變量的一個別名。那麼,從這不少人會聯想到,引用會不會也只是一個名字而已,編譯器在生成目標代碼的時候,會用實際地址替換引用呢?

答案並不是這樣!

那咱們接下來看看,當咱們定義一個引用時,發生了什麼:

1     int var = 42; 2 01303AC8  mov         dword ptr [var],2Ah  
3     int&  refVar = var; 4 01303ACF  lea         eax,[var]  
5 01303AD2  mov         dword ptr [refVar],eax

上面的代碼顯示,當定義一個引用時,編譯器將var的地址賦給了以refVar爲地址的一塊內存區域。也就是說refVar其實存放的是var的地址。

這讓咱們聯想到了指針,那麼咱們看看定義一個指針是發生了什麼:

1     int var = 42; 2 01213AC8  mov         dword ptr [var],2Ah  
3     int* ptrVar = &var; 4 01213ACF  lea         eax,[var]  
5 01213AD2  mov         dword ptr [ptrVar],eax

沒錯,沒有任何差異,定義一個引用和一個指針的彙編代碼徹底一致!

3、const哪裏去了

相信從上面的分析時,你可能已經相信了,引用實際上就是一個指針。那麼爲何說引用是一個常量指針呢,在目標代碼裏有什麼體現呢?

這個問題其實要從C++底層機制談起,C++爲咱們提供的各類存取控制僅僅是在編譯階段給咱們的限制,也就是說編譯器確保了你在完成任務以前的正確行爲,若是你的行爲不正確,那麼編譯器就是給你在編譯時提示錯誤。所謂的const和private等在實際的目標代碼里根本不存在,因此在程序運行期間只要你願意,你能夠經過內存工具修改它的任何一個變量的值。

這也就解釋了爲何上面的兩段代碼中引用和指針的彙編代碼徹底一致。

C++設計引用,並用常量指針來從編譯器的角度實現它,目標是爲了提供比指針更高的安全性,由於常量指針一旦與變量地址綁定將不能更改,這樣下降了指針的危險係數,它提供了一種一對一的指針。

可是你以爲使用引用就安全了嗎?它一樣會有與使用指針同樣的問題

1 int *var = new int(42); 
2 int &ref = *var; 
3 delete var; 
4 ref = 42; 
5 return 0;

上面這段代碼就很不安全,由於ref引用的內存區域不合法。

爲了進一步驗證引用與指針在本質上的相同,咱們看當引用做爲函數參數傳遞時,編譯器的行爲:

複製代碼

 1 void Swap(int& v1, int& v2);  2 void Swap(int* v1, int* v2); 3  4     int var1 = 1;  5 00A64AF8  mov         dword ptr [var1],1   6     int var2 = 2;  7 00A64AFF  mov         dword ptr [var2],2   8     Swap(var1,var2);  9 00A64B06  lea         eax,[var2]  
10 00A64B09  push        eax  
11 00A64B0A  lea         ecx,[var1]  
12 00A64B0D  push        ecx  
13 00A64B0E  call        Swap (0A6141Fh)  
14 00A64B13  add         esp,8  15     Swap(&var1, &var2); 16 00A64B16  lea         eax,[var2]  
17 00A64B19  push        eax  
18 00A64B1A  lea         ecx,[var1]  
19 00A64B1D  push        ecx  
20 00A64B1E  call        Swap (0A61424h)  
21 00A64B23  add         esp,8

複製代碼

上面代碼再次證實了,引用與指針的行爲徹底一致,只是編譯器在編譯時對引用做了更嚴格的限制。

4、引用佔多大的內存空間

由於在在表達式中,使用引用實際上就像使用變量自己同樣,因此直接用sizeof是得不到引用自己的大小的。

double var = 42.0; 
double& ref = var;

cout << sizeof var << endl;  // print 8 cout << sizeof ref << endl;   // print 8

咱們能夠經過定義一個只含有引用的類來解決這個問題:

複製代碼

1 class refClass{ 
2 private: 
3     double& ref; 
4 public: 
5     refClass(double var = 42.0) :ref(var){} 
6 };7 8 cout << sizeof refClass << endl;  // print 4

複製代碼

因此結論就是引用和指針同樣實際佔內存空間4個字節。

 

參考文章:http://www.cnblogs.com/rollenholt/articles/1907408.html

做者:☆Ronny丶

出處:http://www.cnblogs.com/ronny/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利。

相關文章
相關標籤/搜索