日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

汇编层探索与讨论c++引用

發布時間:2025/3/21 c/c++ 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 汇编层探索与讨论c++引用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 提出問題
  • 下面提到詞匯前提聲明
    • 第一組:
    • 第二組:
    • 第三組:
    • 第四組:
    • 第五組:
    • 結論:
    • 第七組:
    • 第八組:
    • 第九組:
  • 引用比指針安全????
  • 常量引用

提出問題

定義一個引用類型和將一個變量轉換成引用類型一樣嗎?
引用比指針安全,真的是這樣嗎?對引用不理解的話比指針還危險。
為什么要用常量引用傳參,只是為了只讀?

下面提到詞匯前提聲明

對象:不是OO里面的對象,而是泛指在c++語言中某種類型(內嵌,結構體,類)的實例,與變量相同的意思
存儲體:對象(或變量)的存儲內容的控件,一般是內存

下面用到的變量命名規則:
引用:以r開頭,緊跟接類型縮寫,如float&rf
指針:以p開頭,緊跟接類型縮寫,如float *pf ,const float * pcf,float * const pfc

c++代碼編譯成匯編代碼后,引用和指針同樣是一個 指向內存地址的存儲體(一般是內存單元,或優化后使用寄存器,存放指向的內存地址)

第一組:

uintptr_t uintptr = 0;

所對應的匯編代碼是:

00B81832 C7 45 F4 00 00 00 00 mov dword ptr [uintptr],0

第二組:

float flt = 0.f;

所對應的匯編代碼是:

00B81839 0F 57 C0 xorps xmm0,xmm0 00B8183C F3 0F 11 45 E8 movss dword ptr [flt],xmm0

第三組:

float flt2 = 2.f;

所對應的匯編代碼是:

00B81841 F3 0F 10 05 30 7B B8 00 movss xmm0,dword ptr [__real@40000000 (0B87B30h)] 00B81849 F3 0F 11 45 DC movss dword ptr [flt2],xmm0

第四組:

float& rf = (float&)uintptr;

所對應的匯編代碼是:

00B8184E 8D 45 F4 lea eax,[uintptr] 00B81851 89 45 D0 mov dword ptr [rf],eax

第五組:

float* const pfc = &(float&)uintptr;

所對應的匯編代碼是:

00B81854 8D 45 F4 lea eax,[uintptr] 00B81857 89 45 C4 mov dword ptr [pfc],eax

結論:

不同的是它們的訪問方式,最根本的區別就是,引用的訪問直接訪問被引用目標(直接反引用),而指針的訪問是訪問存放著目標內存地址的存儲體,對目標的訪問必須顯式反引用

也就是說你不能訪問到引用存放的目標內存地址的字符串(除非你強行crack)。當你使用賦值符號=對引用進行賦值時,編譯器首先將反引用為目標,你只能對引用的目標進行賦值操作。指針在沒有使用反引用(*)時,訪問的是存放著目標內存地址的存儲體,也就是你可以對指針內存單元賦值改變指向,而無法對引用單元賦值改變指向。

第七組:

rf = 1;

所對應的匯編代碼是:

004B185A 8B 45 D0 mov eax,dword ptr [rf] 004B185D F3 0F 10 05 30 7B 4B 00 movss xmm0,dword ptr [__real@3f800000 (04B7B30h)] 004B1865 F3 0F 11 00 movss dword ptr [eax],xmm0

同樣當你希望通過&符號訪問引用內存單元地址時,你可是不可能做到的,因為引用先于地址訪問符號,反引用被引用的目標身上,也就是說&訪問到的是被引用的目標的地址。以上面的反匯編來說,當你使用&rf訪問地址時,rf比&先反引用成引用的目標,&就只能訪問到rf引用的目標“dword ptr [rf]”。除非你用匯編代碼強行“lea ? ,[rf]”,你才能取得引用的用于存放引用目標的地址的地址。

第八組:

float* pf = &rf; 0091185F 8B 45 D0 mov eax,dword ptr [rf] 00911862 89 45 B8 mov dword ptr [pf],eax void* pv = (void*)&pfc; 00911865 8D 45 C4 lea eax,[pfc] 00911868 89 45 AC mov dword ptr [pv],eax void* pv2;__asm {lea eax, [rf]; 0091186B 8D 45 D0 lea eax,[rf] mov dword ptr[pv2], eax; 0091186E 89 45 A0 mov dword ptr [pv2],eax

如果說引用是被引用目標的別名,那么將一個變量轉換成引用類型,是否使用這個變量成為了其它目標的別名。

答案顯然不是。那會是什么?先來看看下面這組代碼:

第九組:

rf = 1.; 00425221 8B 45 D0 mov eax,dword ptr [rf] 00425224 F3 0F 10 05 30 7B 42 00 movss xmm0,dword ptr [__real@3f800000 (0427B30h)] 0042522C F3 0F 11 00 movss dword ptr [eax],xmm0 (float&)rf = 2; 00425230 8B 45 D0 mov eax,dword ptr [rf] 00425233 F3 0F 10 05 D0 7B 42 00 movss xmm0,dword ptr [__real@40000000 (0427BD0h)] 0042523B F3 0F 11 00 movss dword ptr [eax],xmm0 (float&)uintptr = 3.; 0042523F F3 0F 10 05 D4 7B 42 00 movss xmm0,dword ptr [__real@40400000 (0427BD4h)] 00425247 F3 0F 11 45 F4 movss dword ptr [uintptr],xmm0 (float&)uintptr = 3.; 0042524C F3 0F 10 05 D4 7B 42 00 movss xmm0,dword ptr [__real@40400000 (0427BD4h)] 00425254 F3 0F 11 45 F4 movss dword ptr [uintptr],xmm0 (int&)rf = 2; 00425259 8B 45 D0 mov eax,dword ptr [rf] 0042525C C7 00 02 00 00 00 mov dword ptr [eax],2 (int&)uintptr = 3; 00425262 C7 45 F4 03 00 00 00 mov dword ptr [uintptr],3

fld是從內存單元加載浮點數到浮點寄存器fstp是將浮點寄存器0的浮點數存儲進內存單元

將變量轉換成引用類型,不是把變量轉換成引用,而是轉換成批向變量的引用,按引用的類型來訪問

uintptr 是一個非引用類型,(float&)uintptr轉換成引用類型,但uintptr不引用其它目標,而是作為一個沒有別名的引用目標使用,或者說對uintptr內存單元改變了對它訪問的類型(與 * ( float * )&uintptr訪問的效果一樣)。而(float)uintptr則是按uintptr聲明的類型讀出它的內存單元數據,然后將數據轉換類型。

至于“(float&)rf=2.”,rf這個引用首先反引用(在rf我們不可能通過c++語言訪問到的指針單元,放著uintptr變量單元的地址,反引用成uintptr),實質就是對引用目標改變了對它訪問的類型,又如“(int &)rf=2;”,并非改變對引用“rf”的訪問類型,而是改變對其引用的目標“uintptr”的訪問類型。

說到這里,引用的引用是禁止的,這就不難理解了。當你有個名為“rf”的引用,你對這個“rf”的訪問(不論讀寫)都會轉化成對引用用目標的訪問(限定在高級語言層)。即使你想定義一個引用去引用這個引用的地址也是無功的。“float &rfx=rf;”這句,rfx并不是在引用rf,而是rf直接反引用成了uintptr,實質就是“float& rfx=uintptr;”。如果你要引用到名為rf的引用,你必須要先通過在c++語言層取得名為rf的引用,它存放指針的內存單元地址,而這樣是不可能的,所以引用的引用是禁止的。

引用比指針安全????

不知從何起流傳引用比指針安全,理由大致如下:

  • 引用定義之時必須初始化
  • 引用不會指向null
  • 引用的指向不可以被修改,只有定義引用的同時才能定義它的指向
  • 1的問題歸根到底是程序員不對變量(類構造)進行初始化,這樣的程序員不少你還改變不了他們。使用引用,編譯器強求他們進行初始化。因為由于沒有寫初始化造成的問題不只專屬于指針。往往有程序員認為花時間寫初始化是在浪費時間,這個多變量我還要一個一個初始化,每個類這么多成員變量我還要一個一個初始化,這么多類我還要一個一個為它們寫幾種構造函數?!!我的工作是按需求寫邏輯不是寫初始化和構造析構函數

    2的問題有點無稽,引用為什么就理所當然不會指向null?

    uintptr_t ptr = 0;0046198F C7 45 C4 00 00 00 00 mov dword ptr [ptr],0 float flt = 0.; 00461996 0F 57 C0 xorps xmm0,xmm0 00461999 F3 0F 11 45 B8 movss dword ptr [flt],xmm0 float flt2 = 2.; 0046199E F3 0F 10 05 30 7B 46 00 movss xmm0,dword ptr [__real@40000000 (0467B30h)] 004619A6 F3 0F 11 45 AC movss dword ptr [flt2],xmm0 flt2 = 2.; 004619AB F3 0F 10 05 30 7B 46 00 movss xmm0,dword ptr [__real@40000000 (0467B30h)] 004619B3 F3 0F 11 45 AC movss dword ptr [flt2],xmm0 float& rf0 = *(float*)0; 004619B8 C7 45 A0 00 00 00 00 mov dword ptr [rf0],0 ;反引用到空地址上 float& rf = *new float(flt); 004619BF 6A 04 push 4 004619C1 E8 4D F7 FF FF call operator new (0461113h) 004619C6 83 C4 04 add esp,4 004619C9 89 85 C8 FE FF FF mov dword ptr [ebp-138h],eax 004619CF 83 BD C8 FE FF FF 00 cmp dword ptr [ebp-138h],0 004619D6 74 1D je main+0C5h (04619F5h) ;new是否分配 004619D8 8B 85 C8 FE FF FF mov eax,dword ptr [ebp-138h] 004619DE F3 0F 10 45 B8 movss xmm0,dword ptr [flt] 004619E3 F3 0F 11 00 movss dword ptr [eax],xmm0 004619E7 8B 8D C8 FE FF FF mov ecx,dword ptr [ebp-138h] 004619ED 89 8D C0 FE FF FF mov dword ptr [ebp-140h],ecx 004619F3 EB 0A jmp main+0CFh (04619FFh) 004619F5 C7 85 C0 FE FF FF 00 00 00 00 mov dword ptr [ebp-140h],0 ;new失敗了,名為rf的引用指向null 004619FF 8B 95 C0 FE FF FF mov edx,dword ptr [ebp-140h] 00461A05 89 55 94 mov dword ptr [rf],edx

    可以看出引用在初始化時,同樣會有寫情況,使它指向空地址。你并不能阻止別人使用指針反引用來對引用進行初始化。的確在使用指針的時候,我們無時無刻都要為安全考慮多寫些指針判斷,但是使用了引用,我們就可以堂而皇之認為安全了。

    此外使用了引用,還必須要注意它和目標之間的生命周期關系。因為一旦使用了引用,就讓“安全”遮眼,不去理會引用目標的生命周期了。因為《effective c++》提到的不要企圖返回函數的局部變量的引用或者函數new出來的對象的引用,這兩處實質都是引用和引用目標之間的生命周期關系的問題。你引用了一個目標,而這個目標在你使用引用的范圍之內就已經被多銷毀。你引用了一個目標,而這個目標的生命周期大于你使用引用的范圍,你應不應該去管理這個目標的生命周期。《effective c++》提到如果你返回了函數new出來的對象的引用,你不得不自己親手去delete引用的目標,這樣使用起來就很奇怪。從使用引用的角度來看,是不想也不要去理會目標的生命周期的,但是它的本質還是一個指針,指針有的問題它身上也一樣會發生。

    比如你寫了一個模塊,模塊的類之間存在引用關系,這些引用在你模塊內的生命周期關系中一切入好。但某天有人或你要重用其中的某些類,這些類聚合的引用由模塊外的類實現代替了,又或者某些類被cache起來,生命周期變大了,引用之間的生命周期關系發生了變化,引用就如指針一樣,同樣可以執行一處無效的地方。因為你是用的是引用而不是指針,你就可能認為引用不會出問題,因為引用是安全的

    3.至于引用不能在定義之外通過語言改變它的指向。當引用同樣有可能被定義指向null的時候,它與指針常量就差異不大了。一直以來都認為指針常量不可能像引用那樣定義的時候,保證指向非null的對象,但是引用會指向null只是一個謊言。那么指針常量有的問題,引用當然也都有。只是引用的過程中,你的賦值訪問都轉化為對目標反引用的賦值操作,而對指針常量進行賦值時,編譯器會報錯指出。但是這樣都無法避免有人不去正確去訪問你的對象, 例如引用是結構體或類的成員變量。只要某處不正確訪問結構體對象或類對象,同樣不論是以指針常量還是引用定義的成員變量,一樣會被修改。

    引用比指針更安全,這只是一個希望,希望通過引用隱藏指針的使用,看起來更加安全。但無論如何引用在匯編層的實現就是指針,讓引用代替指針使編程更加安全,只能夠是一個謊言,當人人都認為這個謊言是真理的時候就是比使用指針更壞的事情,相信引用比指針更加安全,麻痹對待同一樣的事物了。

    c++書籍在將引用相關的話題時,更多的是它作為函數的傳遞參數,與對象傳參作比較。你只需要將對象形參改變成引用形參,你就能訪問對象的訪問對象,還可以避免傳參過程臨時對象的構造,帶來諸多好處。或者在重載類的操作符返回自身的引用,使得類的設計在使用上可以連續書寫。

    常量引用

    說道參數就不得不說一下常量引用,就是引用一個常量了。

    首先你不可以用一個非常量的引用,定義它指向一個常量,你必須用一個常量類型的引用,定義它指向一個常量。那么指向常量會發生什么事情呢?請看下面代碼:

    const float& rcf = 1.f; 00D81A08 F3 0F 10 05 30 7B D8 00 movss xmm0,dword ptr [__real@3f800000 (0D87B30h)] 00D81A10 F3 0F 11 85 7C FF FF FF movss dword ptr [ebp-84h],xmm0 00D81A18 8D 85 7C FF FF FF lea eax,[ebp-84h] 00D81A1E 89 45 88 mov dword ptr [rcf],eax const float& rcf2 = 1.f; 00D81A21 F3 0F 10 05 30 7B D8 00 movss xmm0,dword ptr [__real@3f800000 (0D87B30h)] 00D81A29 F3 0F 11 85 64 FF FF FF movss dword ptr [ebp-9Ch],xmm0 00D81A31 8D 85 64 FF FF FF lea eax,[ebp-9Ch] 00D81A37 89 85 70 FF FF FF mov dword ptr [rcf2],eax

    可以看到編譯器悄悄為常量分配了局部空間,形式就像

    const float implict=1.f;//dword ptr[ebp-84h] const float &rcf=implict; const float implict2=1.f;//dword ptr[ebp-9Ch] const float &rcf2=implict2;

    為什么不打算對其修改器內容的引用形參,使用常量引用呢?讓其只讀是其一,其二是你不能將一個常量去定義一個引用形參。

    void foo(float&); void foo2(const float&);main() { foo(1.f);//error foo2(1.f);//ok }


    那么這個foo2函數是如何接受一個常量,去定義一個常量引用形參呢?請看反匯編代碼:

    foo2(1.f);//ok 003519B8 F3 0F 10 05 30 7B 35 00 movss xmm0,dword ptr [__real@3f800000 (0357B30h)] 003519C0 F3 0F 11 85 98 FE FF FF movss dword ptr [ebp-168h],xmm0 003519C8 8D 85 98 FE FF FF lea eax,[ebp-168h] 003519CE 50 push eax 003519CF E8 F4 F6 FF FF call foo2 (03510C8h) 003519D4 83 C4 04 add esp,4

    同樣,編譯器偷偷為這個常量開辟了一個臨時空間,將其賦值為1.f常量,并定義foo2的形參指向這個沒有名字的臨時變量。

    另外對于臨時類對象,賦值給其它變量,則會拷貝這個臨時類對象,并隨后析構這個臨時類對象。但是如果將一個臨時類對象去定義一個引用呢?

    B(); 00401163 lea ecx,[ebp-59h] 00401166 call B::B (0401019h) 0040116B lea ecx,[ebp-59h] 0040116E call B::~B (0401028h) A& ra = A(); 00401173 lea ecx,[ebp-21h] 00401176 call A::A (040101Eh) 0040117B lea edx,[ebp-21h] 0040117E mov dword ptr [ra],edx C* const pcc = &C(); 00401181 lea ecx,[ebp-5Ah] 00401184 call C::C (0401037h) 00401189 mov dword ptr [pcc],eax 0040118C lea ecx,[ebp-5Ah] 0040118F call C::~C (040103Ch) ...return 0; 004011C7 mov dword ptr [ebp-64h],0 004011CE lea ecx,[ebp-21h] 004011D1 call A::~A (0401023h) 004011D6 mov eax,dword ptr [ebp-64h] } 004011D9 mov esp,ebp 004011DB pop ebp 004011DC ret

    這個局部的臨時對象“A()”生命周期延長到了局部范圍的結束,因為編譯器要保證這個引用不會產生一個迷途引用。而使用指針常量則沒有辦法延長一個臨時對象的生命周期,“C()”這個臨時對象在定義了指針常量pcc的指向后,就馬上析構了,留下了一個迷途指針。

    相同的討論可以看 stackoverflow
    《Why is a c++ reference considered safer than a pointer?》

    結合上面的內容,你能說出下面的指向和發生了什么操作嗎?

    uintptr_t uintptr = 0; float flt = 0.f; float flt2 = 2.f; float& rf = (float&)uintptr; float& rf2 = (float&)uintptr = flt; float& rf3 = (float&)rf2 = flt2; float*& rpf2 = (float*&)uintptr = &flt;

    你說對了嗎,請參看匯編代碼:

    uintptr_t uintptr = 0; 003F5022 C7 45 F4 00 00 00 00 mov dword ptr [uintptr],0 float flt = 0.f; 003F5029 0F 57 C0 xorps xmm0,xmm0 003F502C F3 0F 11 45 E8 movss dword ptr [flt],xmm0 float flt2 = 2.f; 003F5031 F3 0F 10 05 30 7B 3F 00 movss xmm0,dword ptr [__real@3f800000 (03F7B30h)] 003F5039 F3 0F 11 45 DC movss dword ptr [flt2],xmm0 float& rf = (float&)uintptr; 003F503E 8D 45 F4 lea eax,[uintptr] 003F5041 89 45 D0 mov dword ptr [rf],eax float& rf2 = (float&)uintptr = flt; 003F5044 F3 0F 10 45 E8 movss xmm0,dword ptr [flt] 003F5049 F3 0F 11 45 F4 movss dword ptr [uintptr],xmm0 003F504E 8D 45 F4 lea eax,[uintptr] 003F5051 89 45 C4 mov dword ptr [rf2],eax float& rf3 = (float&)rf2 = flt2; 003F5054 8B 45 C4 mov eax,dword ptr [rf2] 003F5057 F3 0F 10 45 DC movss xmm0,dword ptr [flt2] 003F505C F3 0F 11 00 movss dword ptr [eax],xmm0 003F5060 8B 4D C4 mov ecx,dword ptr [rf2] 003F5063 89 4D B8 mov dword ptr [rf3],ecx float*& rpf2 = (float*&)uintptr = &flt; 003F5066 8D 45 E8 lea eax,[flt] 003F5069 89 45 F4 mov dword ptr [uintptr],eax 003F506C 8D 4D F4 lea ecx,[uintptr] 003F506F 89 4D AC mov dword ptr [rpf2],ecx

    總結

    以上是生活随笔為你收集整理的汇编层探索与讨论c++引用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。