亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++中虛函數怎么用

發布時間:2021-06-15 15:58:46 來源:億速云 閱讀:222 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關C++中虛函數怎么用的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

虛函數調用屬于運行時多態,在類的繼承關系中,通過父類指針來調用不同子類對象的同名方法,而產生不同的效果。

C++ 中的多態是通過晚綁定(對象構造時)來實現的。

用法

在函數之前聲明關鍵字 virtual 表示這是一個虛函數,在函數后增加一個 = 0 表示這是一個純虛函數,純虛函數的類不能創建具體實例。

該示例作后文分析使用,一個包含純虛函數的父類,一個重寫了父類方法的子類,一個無繼承的類。

struct Base {
  Base() : val(7777) {}
  virtual int fuck(int a) = 0;
  int val;
};

struct Der : public Base {
  Der() = default;
  int fuck(int a) override { return val + 4396; }
};

struct A {
  A() = default;
  void funny(int a) {}
};

int main() {
  Der der;
  Base *pbase = &der;
  pbase->fuck(sizeof(Der)); // 調用 Der::fuck(int a);

  A a;
  a.funny(sizeof(A)); // A::funny(int a);

  return 3;
}

實現

原來就了解虛函數是通過虛表的偏移來獲取實際調用函數地址來實現的,但是在何時確定這個偏移和具體的偏移細節也沒有說明,今兒個來探探究竟。

拿上面的代碼進行反匯編獲提取部分函數,main,Base::Base(), Base::fuck(), Der::Der(), Der::fuck, A::funny() 如下:

_ZN4BaseC2Ev:
.LFB1:
  .cfi_startproc
  pushq  %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  movq  %rdi, -8(%rbp)  // 還是 main 函數的棧幀 -32(%rpb) 的地址
  leaq  16+_ZTV4Base(%rip), %rdx // 關鍵點來了,取虛表偏移 16 的地址也就是 __cxa_pure_virtual,這里是沒有意義的
  movq  -8(%rbp), %rax
  movq  %rdx, (%rax)   // 將 __cxa_pure_virtual 的地址存放在 地址rax 的內存中(這個例子中也就是main 函數的棧幀 -32(%rpb) 的地方), 
  movq  -8(%rbp), %rax  // 然后往后偏移 8 個字節,也就是跳過虛表指針,對成員變量 val 初始化。
  movl  $7777, 8(%rax)
  nop           // 注:上面是用這個示例中實際的地址帶入的,實際上對于一個有的類的處理是一個通用邏輯的,構造函數傳入的第一個參數 rdi 是 this 指針,由于有虛表存在的影響,這里會修改 this 指針所在地址的內容,也就是虛表的偏移地址(非起始地址)
  popq  %rbp
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE1:
  .size  _ZN4BaseC2Ev, .-_ZN4BaseC2Ev
  .weak  _ZN4BaseC1Ev
  .set  _ZN4BaseC1Ev,_ZN4BaseC2Ev
  .section  .text._ZN3Der4fuckEi,"axG",@progbits,_ZN3Der4fuckEi,comdat
  .align 2
  .weak  _ZN3Der4fuckEi
  .type  _ZN3Der4fuckEi, @function
_ZN3Der4fuckEi:
.LFB3:
  .cfi_startproc
  pushq  %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  movq  %rdi, -8(%rbp)
  movl  %esi, -12(%rbp)
  movq  -8(%rbp), %rax
  movl  8(%rax), %eax  // 成員變量 val,val 是從 rdi 中偏移 8 字節取的值
  addl  $4396, %eax   // val + 4396
  popq  %rbp
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE3:
  .size  _ZN3Der4fuckEi, .-_ZN3Der4fuckEi
  .section  .text._ZN1A5funnyEi,"axG",@progbits,_ZN1A5funnyEi,comdat
  .align 2
  .weak  _ZN1A5funnyEi
  .type  _ZN1A5funnyEi, @function
_ZN1A5funnyEi:
.LFB4:
  .cfi_startproc
  pushq  %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  movq  %rdi, -8(%rbp)
  movl  %esi, -12(%rbp)
  nop
  popq  %rbp
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE4:
  .size  _ZN1A5funnyEi, .-_ZN1A5funnyEi
  .section  .text._ZN3DerC2Ev,"axG",@progbits,_ZN3DerC5Ev,comdat
  .align 2
  .weak  _ZN3DerC2Ev
  .type  _ZN3DerC2Ev, @function
_ZN3DerC2Ev:
.LFB7:
  .cfi_startproc
  pushq  %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  subq  $16, %rsp
  movq  %rdi, -8(%rbp)  // rdi 是取的 main 棧幀 -32(%rbp) 的地址
  movq  -8(%rbp), %rax
  movq  %rax, %rdi
  call  _ZN4BaseC2Ev   // Base 的構造函數,并且又把傳進來的參數作為實參傳進去了,這里跟蹤進去
  leaq  16+_ZTV3Der(%rip), %rdx // 取虛表偏移16字節 _ZN3Der4fuckEi 的地址 
  movq  -8(%rbp), %rax
  movq  %rdx, (%rax)   // rax 在之前的 Base構造函數中是被修改了的,這里將繼續修改內容,前一次的修改失效。
  nop
  leave
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE7:
  .size  _ZN3DerC2Ev, .-_ZN3DerC2Ev
  .weak  _ZN3DerC1Ev
  .set  _ZN3DerC1Ev,_ZN3DerC2Ev
  .text
  .globl main
  .type  main, @function
main:
.LFB5:
  .cfi_startproc
  pushq  %rbp
  .cfi_def_cfa_offset 16
  .cfi_offset 6, -16
  movq  %rsp, %rbp
  .cfi_def_cfa_register 6
  subq  $48, %rsp
  leaq  -32(%rbp), %rax // 取 -32(%rbp) 的地址,對應 Base *pbase;
  movq  %rax, %rdi
  call  _ZN3DerC1Ev   // 調用了構造函數,并且以-32(%rbp) 的地址作為參數,這里跟蹤進去
  leaq  -32(%rbp), %rax // -32(%rbp) 被修改,該內存中的內容為 Der 虛表的偏移地址 
  movq  %rax, -8(%rbp)
  movq  -8(%rbp), %rax
  movq  (%rax), %rax  // rax = M[rax],取出虛表偏移中的地址
  movq  (%rax), %rdx  // rdx = M[rax] , 取出虛表偏移的內容(也就是函數地址),算上上面這是做了兩次解引用
  movq  -8(%rbp), %rax
  movl  $16, %esi    // sizeof(Der) = 16, 包含一個虛表指針和 int val;
  movq  %rax, %rdi   // 虛表偏移中的地址
  call  *%rdx      // 調用函數
  leaq  -33(%rbp), %rax
  movl  $1, %esi
  movq  %rax, %rdi
  call  _ZN1A5funnyEi  // 普通成員函數,實現簡單
  movl  $3, %eax
  leave
  .cfi_def_cfa 7, 8
  ret
  .cfi_endproc
.LFE5:
  .size  main, .-main
  .weak  _ZTV3Der
  .section  .data.rel.ro.local._ZTV3Der,"awG",@progbits,_ZTV3Der,comdat
  .align 8
  .type  _ZTV3Der, @object
  .size  _ZTV3Der, 24
_ZTV3Der:
  .quad  0
  .quad  _ZTI3Der
  .quad  _ZN3Der4fuckEi // Der::fuck(int a);
  .weak  _ZTV4Base
  .section  .data.rel.ro._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat
  .align 8
  .type  _ZTV4Base, @object
  .size  _ZTV4Base, 24
_ZTV4Base:
  .quad  0
  .quad  _ZTI4Base
  .quad  __cxa_pure_virtual // 純虛函數,無對應符號表
  .weak  _ZTI3Der
  .section  .data.rel.ro._ZTI3Der,"awG",@progbits,_ZTI3Der,comdat
  .align 8
  .type  _ZTI3Der, @object
  .size  _ZTI3Der, 24

現在是一個純虛函數,類中也沒有虛析構函數,通過反匯編來看一些這個實現。

_ZTV3Der_ZTV4Base 是兩個虛表,大小為 24, 8 字節對齊,分別對應 Der 子類和 Base 父類。虛表中偏移 16 字節(偏移大小可能和實現相關)為虛函數地址,每次構造函數的被調用的時候,會將該偏移地址存儲到父類指針所在內存中,所以在上代碼中看到,在 Base 和 Der 類的構函數中都出現了設置偏移地址的操作,但是子類構造函數會覆蓋父類的修改。這樣一來,實際的函數運行地址依賴構造函數,子類對象被構造就調用子類的方法,父類構造就調用父類的方法(非純虛函數),實現了運行時多態。

增加一個虛函數后, 后面的虛函數地址就添加到虛表之中,如下

virtual void Base::shit() {}
void Der::shit() override {}

_ZTV3Der:
  .quad  0
  .quad  _ZTI3Der
  .quad  _ZN3Der4fuckEi
  .quad  _ZN3Der4shitEv
  .weak  _ZTV4Base
  .section  .data.rel.ro._ZTV4Base,"awG",@progbits,_ZTV4Base,comdat
  .align 8
  .type  _ZTV4Base, @object
  .size  _ZTV4Base, 32
_ZTV4Base:
  .quad  0
  .quad  _ZTI4Base
  .quad  __cxa_pure_virtual
  .quad  _ZN4Base4shitEv
  .weak  _ZTI3Der
  .section  .data.rel.ro._ZTI3Der,"awG",@progbits,_ZTI3Der,comdat
  .align 8
  .type  _ZTI3Der, @object
  .size  _ZTI3Der, 24

再調用另外一個虛函數就簡單很多了,直接地址進行偏移(這里shit在fuck之后,所以+8)

 movq  -8(%rbp), %rax
  movq  (%rax), %rax
  addq  $8, %rax
  movq  (%rax), %rdx
  movq  -8(%rbp), %rax
  movq  %rax, %rdi
  call  *%rdx

簡單畫了一下虛函數運行的內存結構圖

C++中虛函數怎么用

感謝各位的閱讀!關于“C++中虛函數怎么用”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

五台县| 宣武区| 武汉市| 澜沧| 鹤庆县| 长春市| 布尔津县| 康乐县| 昌宁县| 莒南县| 西丰县| 天全县| 南昌县| 拉萨市| 彭泽县| 望谟县| 昌黎县| 泸西县| 漳州市| 武隆县| 曲周县| 樟树市| 太康县| 南乐县| 水富县| 鄯善县| 武邑县| 丰台区| 大同县| 高陵县| 贺兰县| 沾益县| 龙里县| 盐边县| 崇义县| 洪泽县| 吴堡县| 峡江县| 伊宁县| 缙云县| 左贡县|