參考:http://blog.csdn.net/wanwenweifly4/article/details/6739687 紅色是我添加的,其他地方是原作者的。 主要是看了上面的這篇“從底層匯編理解 c++ 引用實現(xiàn)機制“的文章之后,覺得不錯。就轉(zhuǎn)了過來,同時,對文中的程序都在自己的機器上驗證了一下。 使用的G++版本:g++ (GCC) 4.5.1 20100924 如果要查看匯編后代碼與源碼的關(guān)系,我用的方法是: 先用g++生成帶有調(diào)試信息的目標(biāo)文件:g++ -g -c ref.cc 然后再利用objdump命令查看目標(biāo)文件ref.o:objdump -S ref.o 引用類型到底是什么?它和指針有什么關(guān)系?它本身占用內(nèi)存空間嗎? 帶著這些疑問,我們來進行分析。 先看代碼: #include <stdio.h> #include <iostream> using namespace std; void main() { int x = 1; int &b = x; } | int main() { int x=1; int &b=x; return 0; } | 通過匯編查看代碼如下:
9: int x = 1; 00401048 mov dword ptr [ebp-4],1 10: int &b = x; 0040104F lea eax,[ebp-4] 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,因為棧內(nèi)的變量內(nèi)存是從高往低進行分配的。所以b的地址比x的低。 lea eax,[ebp-4] 這條語句將x的地址ebp-4放入eax寄存器 mov dword ptr [ebp-8],eax 這條語句將eax的值放入b的地址ebp-8中 上面兩條匯編的作用即:將x的地址存入變量b中,這不和將某個變量的地址存入指針變量是一樣的嗎? 所以從匯編層次來看,的確引用是通過指針來實現(xiàn)的。 下面我們通過程序來驗證,我們知道,在程序一層我們只要直接涉及到引用變量的操作,我們操作的總是被引用變量,即編譯器幫我們做了些手腳,總是在引用前面加上*。所以我們要讀取真正的“引用變量的值”,必須采取一定的策略,好吧,我們就按照變量在棧中分布的特點來繞過編譯器的這個特點。 [cpp] view plaincopyprint? #include <stdio.h> #include <iostream> using namespace std; void 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)); } 輸出結(jié)果為:&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; } 輸出結(jié)果是: &x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460 這個地方的結(jié)果和作者不一樣,可以看后面的解釋。 |
void main() { int x = 1; int &b = x; printf("&x=%x,&b=%x\n",&x,&b); } 輸出結(jié)果為:&x=12ff7c,&b=12ff7c. | #include <cstdio> int main() { int x=1; int &b=x; printf("&x=%x,&b=%x\n",&x,&b); return 0; } 輸出結(jié)果:&x=bfe74aa8,&b=bfe74aa8 |
b的地址我們沒法通過&b獲得,因為編譯器會將&b解釋為:&(*b) =&x ,所以&b將得到&x。也驗證了對所有的b的操作,和對x的操作等同。 但是我們可以間接通過&y-1來得到b的地址,從而得到b的值:*(&y-1) 從結(jié)果可以知道,b的值即x的地址,從而可以知道,從地層實現(xiàn)來看,引用變量的確存放的是被引用對象的地址,只不過,對于高級程序員來說是透明的,編譯器 屏蔽了引用和指針的差別。 下面是程序的變量在內(nèi)存棧中的分布,引用變量一樣也占用內(nèi)存空間,而且應(yīng)該是4個字節(jié)的空間。 雖然從底層來說,引用的實質(zhì)是指針,但是從高層語言級別來看,我們不能說引用就是指針,他們是兩個完全不同的概念。有人說引用是受限的指針,這種說法我不贊同,因為從語言級別上,指針和引用沒有關(guān)系,引用就是另一個變量的別名。對引用的任何操作等價于對被引用變量的操作。從語言級別上,我們就不要去考慮它的底層實現(xiàn)機制啦,因為這些對你是透明的。所以在面試的時候,如果面試的人問到這個問題,可以先從語言級別上談?wù)勔?,深入的話就從底層的實現(xiàn)機制進行分析。而不能什么條件沒有就說:引用就是指針,沒有差別,......之類的回答  對于下面的程序: #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; } 我的結(jié)果是&x=bfe1b308,&y=bfe1b304,&b=bfe1b300,b=8048460 與原作者的不同,然后我就將上面的程序進行編譯得到下面的結(jié)果: 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中地址所指向的內(nèi)容傳給寄存器%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的內(nèi)容傳給堆棧0x10處 35: 89 44 24 0c mov %eax,0xc(%esp) 將%eax的內(nèi)容傳給堆棧0xc處 39: 8d 44 24 24 lea 0x24(%esp),%eax 將堆棧0x24處的地址傳給寄存器%eax 3d: 89 44 24 08 mov %eax,0x8(%esp) 將%eax的內(nèi)容傳給堆棧0x8處 41: 8d 44 24 28 lea 0x28(%esp),%eax 將堆棧0x28處的地址傳給寄存器%eax 45: 89 44 24 04 mov %eax,0x4(%esp) 將%eax的內(nèi)容傳給堆棧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標(biāo)準(zhǔn)中提到的函數(shù)的參數(shù)是逆序入棧的。 為了驗證上面的想法,將原程序中的*(&y-1)改為*(&x+1) 結(jié)果為: &x=bf9a74c8,&y=bf9a74c4,&b=bf9a74cc,b=bf9a74c8 這樣就和作者的符合起來了。
|