2020-12-6(从反汇编理解指针和引用的区别)
這是我10個(gè)月前看到的一篇博客吧,感覺分析指針和引用的文章這是我目前見過講解得最清晰的一篇:
本文主要基于反匯編代碼,從初始化、賦值以及取地址三個(gè)角度來理解指針和引用的區(qū)別。
初始化
寫出以下代碼并查看反匯編代碼:
在初始化階段,指針和引用的行為都是一樣的:先將x的地址加載到寄存器eax中,然后把eax的值拷貝到另一個(gè)內(nèi)存地址中。
從反匯編中可以看到,x的地址就是ebp-0ch,對(duì)于指針來說,用x的地址來初始化指針ptr實(shí)際上就是把x的地址ebp-0c放到了ptr的地址單元中,而ptr的地址則是ebp-18h;
對(duì)于引用來說,這里就有點(diǎn)問題了:常說的引用都是“變量的別名”,似乎ref和x就應(yīng)該是同一個(gè)地址,而實(shí)際上這里做了和指針初始化相同的操作——把x的地址放到了另一個(gè)內(nèi)存單元(地址為ebp-24h)中。其實(shí)在這就可以有一種猜測:雖然ref是x的引用,但是ref也有自己的地址的,而在初始化階段,它的地址單元中存放的是它所引用的變量x的地址。如下所示:
為了便于敘述,下文中把ref叫做“引用變量”,來表明它也是一個(gè)有地址的“變量”(這種說法并不準(zhǔn)確,只是暫時(shí)找不到其它說法)。
賦值
由于自加自減也是一個(gè)賦值的過程,為了便于敘述,這里就用自加來進(jìn)行分析。寫出以下代碼并查看反匯編代碼:
從反匯編代碼可以看到,指針的自加和引用的自加是不同的,引用的步驟更多。
在指針方面,一共有三步:①把指針變量ptr內(nèi)存單元中的數(shù)據(jù)拷貝到eax;②eax加4(這里的4對(duì)應(yīng)32位編譯器下int型的size);③將eax的值寫回到指針變量ptr的內(nèi)存單元中。這三個(gè)步驟其實(shí)就做了一件事:把指針變量ptr內(nèi)存單元中的數(shù)據(jù)加4。而在此之前指針變量ptr的內(nèi)存單元中存放的是變量x的地址,因此,這就相當(dāng)于斷開了ptr和x之間的聯(lián)系;
在引用方面,一共有五步:①把引用變量ref內(nèi)存單元中的數(shù)據(jù)拷貝到eax;②根據(jù)eax的值找到相應(yīng)的內(nèi)存單元并把該內(nèi)存單元中的數(shù)據(jù)拷貝到ecx中;③ecx加1;④再次把引用變量ref內(nèi)存單元中的數(shù)據(jù)拷貝到edx中;⑤把ecx的值寫到edx的值對(duì)應(yīng)的內(nèi)存單元中。要分析這五步做了什么,一定要知道在此之前,引用變量ref的內(nèi)存單元中存的是變量x的地址。這五步做的事就是:根據(jù)ref存放的x的地址來找到x的內(nèi)存單元,然后把x的內(nèi)存單元中的值加1。從這個(gè)過程可以知道,ref++;表面上是對(duì)ref進(jìn)行自加,而實(shí)際上,ref只是一個(gè)媒介,通過ref找到x,然后對(duì)x進(jìn)行自加。
通過對(duì)二者賦值,可以發(fā)現(xiàn),引用變量ref的確有自己的地址,它在內(nèi)存中是占空間的。并且指針變量ptr完全具有“主導(dǎo)權(quán)”,對(duì)指針變量ptr進(jìn)行賦值,改變的就是它本身;而引用變量ref則完全沒有“主導(dǎo)權(quán)”,對(duì)引用變量ref進(jìn)行賦值,改變的并不是它本身,而是通過它所找到的x。如下所示:
取地址
對(duì)ptr和ref分別進(jìn)行取地址,這里的變量t和t1可以不用管它,只需要觀察取地址的過程。查看相應(yīng)反匯編代碼:
由于不需要管t和t1,因此只需要關(guān)注ptr和ref取地址各自反匯編代碼第一行即可。
在指針方面,取出ptr的地址&ptr是通過lea eax,[ebp-18h]來實(shí)現(xiàn)的。這句匯編代碼的意思是,把ebp-18h這個(gè)地址,加載到eax中。而在ebp-18h就是指針變量ptr的內(nèi)存地址,因此,指針變量ptr取地址很直接,就是取出ptr的內(nèi)存地址;
再看引用方面,取出ref的地址&ref是通過mov eax,dword ptr [ebp-24h]實(shí)現(xiàn)的。這句匯編代碼的意思是,取出ebp-24h這個(gè)地址的內(nèi)存單元中的值,拷貝到eax中。根據(jù)前面已經(jīng)知道,ebp-24h是ref的地址,在這個(gè)地址中存放的是ref引用的x的地址,因此&ref實(shí)際上是x的地址。
由此可以發(fā)現(xiàn),對(duì)指針取地址,取出的就是指針變量的地址,而對(duì)引用取地址,取出的實(shí)際上是引用變量所在內(nèi)存單元中的值,也就是它引用的變量的地址。
總結(jié)
1.引用和指針都是占用內(nèi)存的。引用變量和指針變量各自的內(nèi)存單元中,存放的都是它們引用或指向的變量的地址;
2.引用實(shí)際上是把引用變量本身給隱藏了,表面上對(duì)引用變量進(jìn)行賦值,實(shí)際上改變的是它引用的變量的值;表面上是對(duì)引用變量取地址,實(shí)際上取出來的是它引用的變量的地址;
3.由于引用變量的操作的實(shí)際對(duì)象都是引用變量所在內(nèi)存單元中的值,因此引用變量必須和另一個(gè)變量關(guān)聯(lián)起來。也就是說,引用必須初始化。原因很簡單,如果你不對(duì)一個(gè)引用進(jìn)行初始化,那么引用變量的內(nèi)存單元中存放的都是垃圾數(shù)據(jù),后面對(duì)引用變量進(jìn)行操作時(shí)就會(huì)通過這些垃圾數(shù)據(jù)找到對(duì)應(yīng)的內(nèi)存地址,這是非常危險(xiǎn)的;
那么直接初始化的時(shí)候?qū)σ觅x一個(gè)常量值呢?這實(shí)際上也是不對(duì)的,因?yàn)橐玫某跏蓟瘜?shí)際是用用來初始化的變量的地址來初始化引用變量它自己的內(nèi)存單元,而一個(gè)常量值哪有地址呢?雖然在C++11中已經(jīng)可以用一個(gè)常量來初始化右值引用,但是通過反匯編可以看到,右值引用的根本,還是先用一個(gè)地址去保存這個(gè)常量值,然后再用這個(gè)地址去初始化引用變量。
而指針變量則完全不一樣了,指針變量的操作的實(shí)際對(duì)象都是它自己,因此指針變量不像引用那樣必須初始化,但是為了安全,也應(yīng)該初始化。
4.依然是由于引用變量的一切操作實(shí)際對(duì)象都是它引用的變量,因此從用戶角度來說是沒有入口讓用戶去修改引用變量本身的。換句話說,用戶層面沒有任何辦法去改變引用變量內(nèi)存單元中存放的地址,這也就是為什么引用一旦初始化后就無法再修改。
5.由于引用變量自身對(duì)于用戶是不可見的,對(duì)引用變量取地址得到的也不是引用變量的地址,因此你無法讓一個(gè)引用變量的內(nèi)存中存放另一個(gè)引用變量的地址,換句話說,不存在引用的引用。而相反,由于指針變量取出來的地址就是它本身的地址,因此你完全可以把一個(gè)指針變量的地址存放在另一個(gè)指針變量的內(nèi)存中,這也就是為什么可以存在多級(jí)指針,但是多級(jí)引用是不允許的。
6.關(guān)于“引用是變量的別名”的考慮。感覺這句話既對(duì)也不對(duì),說它不對(duì)是因?yàn)橐米兞亢鸵玫淖兞慷邔?shí)際上是獨(dú)立的兩塊內(nèi)存單元,只不過前者依托后者而存在,但是這并不能說前者就是后者的別名,這是矛盾的;說它對(duì),是因?yàn)閷?duì)引用變量進(jìn)行操作時(shí),改變的對(duì)象實(shí)際上都是引用的變量而不是引用變量,這就感覺引用變量只是一層偽裝,真正的還是它引用的變量,從這個(gè)角度來說,引用確實(shí)可以說是變量的別名。
7.函數(shù)傳指針與傳引用的區(qū)別。函數(shù)傳指針的原型類似于int add(int * a, int * b);這類函數(shù)的形參是指針,調(diào)用方式為int x = 1, y = 2; add(&x,&y);那么傳入的實(shí)參則是變量的地址,因此在函數(shù)內(nèi)部,是用地址型實(shí)參&x和&y來初始化形參a和b,相當(dāng)于int *a = &x,int * b = &y的;因此形參a和b內(nèi)存單元中存放的是x和y的地址。
而對(duì)于函數(shù)傳引用,函數(shù)原型則類似于int add(int & a,int & b),這類函數(shù)的形參是引用,調(diào)用方式為int x = 1, y = 2; add(x,y);傳入的實(shí)參就是x和y變量自身,在函數(shù)內(nèi)部,則是用x和y來初始化a和b,相當(dāng)于int &a = x,int &b = y,因此,形參a和b內(nèi)存單元中存放的也是x和y的地址,不過它畢竟是引用,如前面所說,當(dāng)你試圖對(duì)形參a或b的值進(jìn)行改變的時(shí)候,改變的不是它內(nèi)存中存放的&x和&y,其實(shí)是x或y;當(dāng)你對(duì)形參a或b取地址時(shí),取出來的并不是形參本身的地址,而是它內(nèi)存中存放的&x和&y。
而再說一個(gè)與本文無關(guān)的函數(shù)傳值,如int add(int a,int b);這類函數(shù)的形參只是普通的int型變量,當(dāng)調(diào)用add(x,y)時(shí),在函數(shù)內(nèi)部實(shí)際上就是用x和y來初始化a和b,相當(dāng)于int a = x,int b = y,所以形參和實(shí)參實(shí)際上只是值相同而已,改變形參并不會(huì)影響實(shí)參。
總結(jié)
以上是生活随笔為你收集整理的2020-12-6(从反汇编理解指针和引用的区别)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020-12-5(操作系统---设备管
- 下一篇: JUSTCTF校赛安卓wp