天天看點

C++引用的本質——另一種指針

本文不探讨羅列引用的概念,什麼函數傳參之類的,這些基礎概念和用法很容易搜到~!

本文主要探讨引用和指針在c語言的下一層——即彙編或者确切的說是僞彙編(at&t僞彙編都一樣的代碼,你指望下層x86彙編還能不一樣麼~)——的實作過程,來摸索一下他們的特點與本質。

首先,引用(reference)在c中沒有,是c++ 才有的概念~! 要用g++編譯器。

定義:引用就是某個目标變量的“别名”(alias)

在我看來,這個“目标變量”也要加上引号,看“目标變量”到底是怎麼定義的了。如果“目标變量”由變量名和值組成,那引用應該是不包含“變量名”這部分的,說白了,覺得他就是一個“新變量”,隻是他和原變量的“值”(即,目标位址,存儲内容)是共用的。

執行個體測試:

C++引用的本質——另一種指針

用g++編譯,gdb調試:

C++引用的本質——另一種指針

可以看到,讓refa引用a的過程,其實就是提取位址(lea),并且占用了棧空間。和指針的實作是一模一樣的。不管你“理論上”怎麼說,至少在實作上(至少在linux的實作上),他就是指針。

可以看到,操作都是直接或者間接的對a的原位址0x10(%esp)進行操作,這個沒什麼問題。但是說引用不占位址是錯誤的,作為一個“指針”他至少占用4位元組吧~!

這是代碼後續的指派操作:

breakpoint 2, main () at ref2.cpp:13

13              a= 2;

1: x/i $pc

=> 0x804868d <main()+153>: movl   $0x2,0x10(%esp)

breakpoint 3, main () at ref2.cpp:18

18             

refa= 3;

=> 0x8048705 <main()+273>: mov   0x14(%esp),%eax

(gdb) si

0x08048709     18              refa = 3;

=> 0x8048709 <main()+277>: movl  $0x3,(%eax)

22              *ptra= 3;

=> 0x804877f <main()+395>:  mov   0x18(%esp),%eax

0x08048783     22             

*ptra = 3;

=> 0x8048783 <main()+399>: movl  $0x3,(%eax)

可以看到引用和指針,從定義到指派,實作都是一樣的。

雖然引用和指針的意義,認為差不多,但是使用方法還是有差别的,想獲得右值,引用直接用變量名,指針要加*操作符。而對引用使用*操作是不允許的。

另外,不同于指針,引用在聲明的時候必須初始化,

但引用可能隻能一次初始化而不能改變引用“目标”嗎?

至少通過如下方法是不能的:

int a = 1;

int b = 2;

int &refa = a;

refa =  b;

這相當于指派為b,即指派為2,連a的值都會變成2.

&refa = &b;

也是不可能的,因為&refa不是左值。

refa = &b;

更不對了,因為這也相當于指派,不過不是2了,是b的位址(列印成10進制,類似于-1075934160這種),并且,需要強制轉換:

refa = (int)&b;

說再多都是yy,實踐出真知~!

圍繞我的”引用即指針“的理念,再做一個摸索,既然認為引用是指針了,那麼sizeof任何指針,包括double的,肯定都是4(我的32位機)。我定義一個double的引用,看看sizeof結果如何(右側為輸出結果):

C++引用的本質——另一種指針

這個結果倒是沒誇張到直接讓ref變成pointer。sizeof(refd)還是按普通的double來算大小,而不是直接按指針來算的。但是也情有可原吧,都說了,雖然他的底層實作和指針一樣,但是sizeof()需要的是傳回類型,它的傳回類型——即”操作級别“,還是比指針要低的。

最後:到底怎樣了解引用更好?

首先,不太同意“引用就是一個别名,不占用記憶體空間“的說法,至少這句話可以再嚴謹點——”引用不為值再去開辟一個位址空間,但是其本身要占用空間~!“

奇了怪了,引用确實占用棧空間,也确實是存儲了目标變量的位址~~~那既然有空間,就應該和指針一樣,我改變你的值不就等于改變你的指向了麼?

但是,因為它和指針不在同一個“操作級别”上,它的”值“又不是位址,也不能像指針那樣改變他的指向。

(“操作級别”是通過存儲内容來判定的,比如普通變量的存儲内容是“值”,而指針的存儲内容是“位址”,可以通過指針獨特的“*”操作來判斷這個“級别”)

個人傾向于認為引用本身就是一種指針,至于他又不能像指針一樣進行重定向等操作,覺得這些完全是語言級别或者說編譯器的刻意限制,隻是一種規則,沒有其他原因。

再次懷疑人生——編譯器的本質如何?到底什麼叫做程式設計語言?各層語言界限如何?從這麼多的實踐操作經驗來總結,似乎也逐漸了解了些,如果再去看看《編譯原理》,或許會有所收獲。

C++引用的本質——另一種指針
C++引用的本質——另一種指針

圍繞我的”引用即指針“的理念,再做一個摸索。既然認為引用是指針了,那麼sizeof任何指針,包括double的,肯定都是4(我的32位機)。我定義一個double的引用,看看sizeof結果如何(右側為輸出結果):

C++引用的本質——另一種指針

這個結果倒是沒誇張到直接讓ref變成pointer。sizeof(refd)還是按普通的double來算大小,而不是直接按指針來算的。但是也情有可原吧,都說了,雖然他的底層實作和指針一樣,但是sizeof()需要的是傳回類型,它的傳回類型——即”操作級别“,還是比指針要低的,和普通的變量相仿。

再次懷疑人生——反正翻譯成下層的東西,都是那點破事,轉換成最後就是一些位址一些寄存器,你能找到位址你就能改(不能改的話,又是哪層編譯器或者彙編器限制你的呢?)~!那麼,編譯器的本質如何?到底什麼叫做程式設計語言?各層語言界限如何?從這麼多的實踐操作經驗來總結,似乎也逐漸了解了些,如果此時再去看看《編譯原理》,或許會有所收獲。

完~!

------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ---------------

other:

寫的是關于引用的,但是通過用gdb調試,個人還是有其他方面的收獲:

例如,at&t彙編中括号的含義,目測帶括号是取位址,不帶括号是原寄存器,好像和之前《計算機組成原理》的僞指令規則差不多。

對比:

存入數值到eax寄存器——用%eax。

存入數值到eax寄存器所儲存的記憶體位址處——用(%eax)。

esp的操作同樣如此:比如0x18(%esp)應該是從esp取出記憶體位址,再加上0x18偏移量。

還有,之前看linux的僞彙編,esp一般都是不變的,變的是偏移值,使用類似于0x1c(%esp)的形式進行操作。

每次編譯運作,esp起始都是230結尾的(系統決定,具體:0xbffff230),但是通過本例觀察,說esp不變是不準确的,執行系統調用,涉及各種庫的時候,一直在變:從230到22c、228、224。。。等于棧下移了?在同一函數内不移,切換了才移?

C++引用的本質——另一種指針

也許試試嵌套個函數什麼的也會有發現~

關于棧指針怎麼跳轉,甚至發生函數跳轉時十幾個寄存器到底儲存上下文需要幾個,而這幾個壓棧又是怎麼壓的,有一個規則,按順序壓,按倒序取?這又是另外一篇日志要探索的事情了。

其他未完成作業:

看看const的實作又是怎樣的,是否有什麼特殊的方法規定”隻讀“,比如轉存寄存器之類的。

使用指針作為函數的參數雖然也能達到與使用引用的效果,但是,在被調函數中同樣要給形參配置設定存儲單元,且需要重複使用"*指針變量名"的形式進行運算,這很容易産生錯誤且程式的閱讀性較差;另一方面,在主調函數的調用點處,必須用變量的位址作為實參。而引用更容易使用,更清晰。

(待驗證)

------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ----------------

繼續閱讀