說到引用,一般c++的教材中都是這麼定義的:
1,引用就是乙個物件的別名。那你有沒有想過,上面的定義正確嗎?編譯器是如何解釋引用的?2,引用不是值不佔記憶體空間。
3,引用必須在定義時賦值,將變數與引用繫結。
這裡先給出引用的本質定義,後面我們再進一步論證。
1,引用實際是通過指標實現的。我們從最簡單的變數的定義開始,看編譯器會做哪些事情。2,引用是乙個常量指標。
3,引用在記憶體中佔4個位元組。
4,在對引用定義時,需要對這個常量指標初始化。
int var = 42;mov dword ptr [var],2ah // 對應彙編**
上面語句申請了一塊記憶體空間,佔4個位元組,存放了乙個int型的變數。記憶體裡放的是42的二進位製碼。
那麼var這個變數名放在哪呢?
我們知道程式如果訪問記憶體裡的資料,需要通過位址來進行訪問,所以上面的**在經過編譯器生成目標**時,用存放42的位址了所有的var,所以結論時,目標檔案中不存在var,所以變數名本身是不佔記憶體的。
而我們知道,引用是變數的乙個別名。那麼,從這很多人會聯想到,引用會不會也只是乙個名字而已,編譯器在生成目標**的時候,會用實際位址替換引用呢?
答案並非這樣!
那我們接下來看看,當我們定義乙個引用時,發生了什麼:
1int var = 42;2
01303ac8
movdword ptr [var],2ah
3int& refvar = var;
401303acf
leaeax,[var]
501303ad2
mov dword ptr [refvar],eax
上面的**顯示,當定義乙個引用時,編譯器將var的位址賦給了以refvar為位址的一塊記憶體區域。也就是說refvar其實存放的是var的位址。
這讓我們聯想到了指標,那麼我們看看定義乙個指標是發生了什麼:
1int var = 42;2
01213ac8
movdword ptr [var],2ah
3int* ptrvar = &var;
401213acf
leaeax,[var]
501213ad2
mov dword ptr [ptrvar],eax
沒錯,沒有任何差別,定義乙個引用和乙個指標的彙編**完全一致!
相信從上面的分析時,你可能已經相信了,引用實際上就是乙個指標。那麼為什麼說引用是乙個常量指標呢,在目標**裡有什麼體現呢?
這個問題其實要從c++底層機制談起,c++為我們提供的各種訪問控制僅僅是在編譯階段給我們的限制,也就是說編譯器確保了你在完成任務之前的正確行為,如果你的行為不正確,那麼編譯器就是給你在編譯時提示錯誤。所謂的const和private等在實際的目標**裡根本不存在,所以在程式執行期間只要你願意,你可以通過記憶體工具修改它的任何乙個變數的值。
這也就解釋了為什麼上面的兩段**中引用和指標的彙編**完全一致。
c++設計引用,並用常量指標來從編譯器的角度實現它,目標是為了提供比指標更高的安全性,因為常量指標一旦與變數位址繫結將不能更改,這樣降低了指標的危險係數,它提供了一種一對一的指標。
但是你覺得使用引用就安全了嗎?它同樣會有與使用指標一樣的問題
1int *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);34
int var1 = 1;5
00a64af8
mov dword ptr [var1],1
6int var2 = 2;7
00a64aff
mov dword ptr [var2],2
8 swap(var1,var2);
900a64b06
leaeax,[var2]
1000a64b09
push
eax
1100a64b0a
leaecx,[var1]
1200a64b0d
push
ecx
1300a64b0e
call
swap (0a6141fh)
1400a64b13
add esp,8
15 swap(&var1, &var2);
1600a64b16
leaeax,[var2]
1700a64b19
push
eax
1800a64b1a
leaecx,[var1]
1900a64b1d
push
ecx
2000a64b1e
call
swap (0a61424h)
2100a64b23
add esp,8
上面**再次證明了,引用與指標的行為完全一致,只是編譯器在編譯時對引用作了更嚴格的限制。
因為在在表示式中,使用引用實際上就像使用變數本身一樣,所以直接用sizeof是得不到引用本身的大小的。
doublevar = 42.0
; double& ref = var
;cout
<< sizeof
var<< endl; //
print 8
cout << sizeof
ref<< endl; //
print 8
我們可以通過定義乙個只含有引用的類來解決這個問題:
1class
refclass 6};
78 cout << sizeof refclass << endl; //
print 4
所以結論就是引用和指標一樣實際佔記憶體空間4個位元組。
參考文章:
你真的了解C 中的值和引用嗎?(上)
術語解釋 注意,上面我說的都是值型別表示式和引用型別表示式,包括區域性變數和成員 如欄位 屬性 索引器 等。現在,我們來考慮以下問題 對於上面這些問題,您的答案是什麼呢?誤區 值型別到底儲存在哪?在談到值型別和引用型別的區別時,很多初學者常說值型別分配在方法的呼叫棧 或執行緒棧 上,引用型別分配在託...
你真的了解C 中的值和引用嗎?(下)
今天要討論的話題是引數傳遞,這不是實現細節。c 中的引數共分為4種 本文主要討論引數按值傳遞和按引用傳遞的區別,以及值型別和引用型別在按值傳遞和按引用傳遞時的表現。我們均以向方法傳遞引數為例。c 的引數在預設情況下都是按值傳遞的。也就是說,當向方法傳遞引數的時候,會建立乙個新的儲存位置,然後將引數的...
你真的了解Java嗎?
三目運算子規則 如果第二個和第三個運算元具有相同的型別,那麼它就是條件表示式的類 型。換句話說,你可以通過繞過混合型別的計算來避免 煩。如果乙個運算元的型別是 t,t 表示 byte short 或 char,而另乙個運算元是乙個 int 型別的常量表示式,它的值是可以用型別 t 表示的,那麼條件表...