導(dǎo)讀
- 為什么說引用是別名
- 為什么引用作為形參可以修改實參的值
- 為什么引用初始化后無法指向其他對象
- 引用與指針對應(yīng)的匯編代碼
- 對引用的理解
為什么說引用是別名
int main() {
int a = 10;
int &b = a;
b = 20;
return 0;
}

從地址的角度來說,a與b兩者的內(nèi)存地址相同,都存儲著0x14這個數(shù)值,所以b可以理解為a的別名。既然引用是別名,顯然需要通過某個變量初始化,因此引用必須在定義的時候直接通過某個變量初始化。
為什么引用作為形參可以修改實參的值
void test(int &x) {
x = 99;
}
int main() {
int a = 10;
test(a);
return 0;
}


在調(diào)用函數(shù)前,將a的地址存入rdi中,此時可以通過rdi存儲的地址值來對變量a進(jìn)行讀寫操作。

進(jìn)入函數(shù)后,向rdi存儲的內(nèi)存地址中寫入0x63數(shù)據(jù),即十進(jìn)制的99。由于rdi中存儲的是變量a的內(nèi)存地址,所以此時a的值等于99。
為什么引用初始化后無法指向其他對象
例子中的注釋對應(yīng)著匯編代碼:
int main() {
// movl $0xa, -0x8(%rbp)
int a = 10;
// leaq -0x8(%rbp), %rcx
// movq %rcx, -0x10(%rbp)
// movq -0x10(%rbp), %rcx
int &x = a;
// movl $0x14, (%rcx)
x = 20;
// movl $0x63, -0x14(%rbp)
int b = 99;
return 0;
}
可以看到,在對引用賦值時,轉(zhuǎn)成匯編對應(yīng)的是通過寄存器賦值,中間通過movq %rcx, -0x10(%rbp)轉(zhuǎn)存了一下a的內(nèi)存地址。那么,是否可以通過-0x10(%rbp)這個內(nèi)存地址來修改x的指向?

可見,當(dāng)我們向-0x10(%rbp)內(nèi)存地址中寫入變量b的內(nèi)存地址時,引用x的值變成了變量b的值99。雖然可以通過lldb修改引用的指向,但在C++層面很難得知引用的內(nèi)存地址(即使通過代碼中變量存儲地址推導(dǎo)出引用地址,同樣需要直接操作內(nèi)存地址,因為沒有一個變量名代表這個內(nèi)存地址)。
當(dāng)對引用使用&符號時,得到的是變量a的地址,而非引用的地址(請參考為什么說引用是別名這一小節(jié))。
是否可以通過直接修改&引用的值,來達(dá)到改變引用指向?

很遺憾,&x無法賦值,因為&x等于變量a的地址,而a的地址是個常量,常量不能是左值。
引用與指針對應(yīng)的匯編代碼
int main() {
// movl $0xa, -0x8(%rbp)
int a = 10;
// leaq -0x8(%rbp), %rcx
// movq %rcx, -0x10(%rbp)
int &x = a;
// movq %rcx, -0x18(%rbp)
int *p = &a;
return 0;
}
從匯編代碼來看,無法區(qū)分是在操作引用還是在操作指針,兩者的區(qū)別僅在于可以通過&p獲取指針的地址,從而改變指針變量p的指向,但是無法通過&x獲取引用的地址(獲取到的是a的地址)。
對引用的理解
文章開頭已經(jīng)說過,可以把引用理解為變量的別名。但是通過為什么引用初始化后無法指向其他對象這一小節(jié)可以看到,引用其實也是需要開辟內(nèi)存的,只是內(nèi)存地址沒有對應(yīng)的變量名,在這個內(nèi)存地址中存儲著引用的變量的內(nèi)存地址,可以把這塊因為引用而開辟的匿名內(nèi)存地址理解為二級指針。
此處可以類比指針,指針存儲著某個變量的地址,但是同樣有一塊空間存儲著指針,而這塊存儲著指針的內(nèi)存空間就是&指針,即指針的地址,引用其實就是個匿名指針。
Have fun!