欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談C++ 虛函數(shù)分析

 更新時(shí)間:2020年02月07日 09:12:08   作者:小胖西瓜  
這篇文章主要介紹了淺談C++ 虛函數(shù)分析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

虛函數(shù)調(diào)用屬于運(yùn)行時(shí)多態(tài),在類的繼承關(guān)系中,通過父類指針來調(diào)用不同子類對象的同名方法,而產(chǎn)生不同的效果。

C++ 中的多態(tài)是通過晚綁定(對象構(gòu)造時(shí))來實(shí)現(xiàn)的。

用法

在函數(shù)之前聲明關(guān)鍵字 virtual 表示這是一個(gè)虛函數(shù),在函數(shù)后增加一個(gè) = 0 表示這是一個(gè)純虛函數(shù),純虛函數(shù)的類不能創(chuàng)建具體實(shí)例。

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

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)); // 調(diào)用 Der::fuck(int a);

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

  return 3;
}

實(shí)現(xiàn)

原來就了解虛函數(shù)是通過虛表的偏移來獲取實(shí)際調(diào)用函數(shù)地址來實(shí)現(xiàn)的,但是在何時(shí)確定這個(gè)偏移和具體的偏移細(xì)節(jié)也沒有說明,今兒個(gè)來探探究竟。

拿上面的代碼進(jìn)行反匯編獲提取部分函數(shù),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 函數(shù)的棧幀 -32(%rpb) 的地址
  leaq  16+_ZTV4Base(%rip), %rdx // 關(guān)鍵點(diǎn)來了,取虛表偏移 16 的地址也就是 __cxa_pure_virtual,這里是沒有意義的
  movq  -8(%rbp), %rax
  movq  %rdx, (%rax)   // 將 __cxa_pure_virtual 的地址存放在 地址rax 的內(nèi)存中(這個(gè)例子中也就是main 函數(shù)的棧幀 -32(%rpb) 的地方), 
  movq  -8(%rbp), %rax  // 然后往后偏移 8 個(gè)字節(jié),也就是跳過虛表指針,對成員變量 val 初始化。
  movl  $7777, 8(%rax)
  nop           // 注:上面是用這個(gè)示例中實(shí)際的地址帶入的,實(shí)際上對于一個(gè)有的類的處理是一個(gè)通用邏輯的,構(gòu)造函數(shù)傳入的第一個(gè)參數(shù) rdi 是 this 指針,由于有虛表存在的影響,這里會修改 this 指針?biāo)诘刂返膬?nèi)容,也就是虛表的偏移地址(非起始地址)
  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 字節(jié)取的值
  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 的構(gòu)造函數(shù),并且又把傳進(jìn)來的參數(shù)作為實(shí)參傳進(jìn)去了,這里跟蹤進(jìn)去
  leaq  16+_ZTV3Der(%rip), %rdx // 取虛表偏移16字節(jié) _ZN3Der4fuckEi 的地址 
  movq  -8(%rbp), %rax
  movq  %rdx, (%rax)   // rax 在之前的 Base構(gòu)造函數(shù)中是被修改了的,這里將繼續(xù)修改內(nèi)容,前一次的修改失效。
  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) 的地址,對應(yīng) Base *pbase;
  movq  %rax, %rdi
  call  _ZN3DerC1Ev   // 調(diào)用了構(gòu)造函數(shù),并且以-32(%rbp) 的地址作為參數(shù),這里跟蹤進(jìn)去
  leaq  -32(%rbp), %rax // -32(%rbp) 被修改,該內(nèi)存中的內(nèi)容為 Der 虛表的偏移地址 
  movq  %rax, -8(%rbp)
  movq  -8(%rbp), %rax
  movq  (%rax), %rax  // rax = M[rax],取出虛表偏移中的地址
  movq  (%rax), %rdx  // rdx = M[rax] , 取出虛表偏移的內(nèi)容(也就是函數(shù)地址),算上上面這是做了兩次解引用
  movq  -8(%rbp), %rax
  movl  $16, %esi    // sizeof(Der) = 16, 包含一個(gè)虛表指針和 int val;
  movq  %rax, %rdi   // 虛表偏移中的地址
  call  *%rdx      // 調(diào)用函數(shù)
  leaq  -33(%rbp), %rax
  movl  $1, %esi
  movq  %rax, %rdi
  call  _ZN1A5funnyEi  // 普通成員函數(shù),實(shí)現(xiàn)簡單
  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 // 純虛函數(shù),無對應(yīng)符號表
  .weak  _ZTI3Der
  .section  .data.rel.ro._ZTI3Der,"awG",@progbits,_ZTI3Der,comdat
  .align 8
  .type  _ZTI3Der, @object
  .size  _ZTI3Der, 24

現(xiàn)在是一個(gè)純虛函數(shù),類中也沒有虛析構(gòu)函數(shù),通過反匯編來看一些這個(gè)實(shí)現(xiàn)。

_ZTV3Der_ZTV4Base 是兩個(gè)虛表,大小為 24, 8 字節(jié)對齊,分別對應(yīng) Der 子類和 Base 父類。虛表中偏移 16 字節(jié)(偏移大小可能和實(shí)現(xiàn)相關(guān))為虛函數(shù)地址,每次構(gòu)造函數(shù)的被調(diào)用的時(shí)候,會將該偏移地址存儲到父類指針?biāo)趦?nèi)存中,所以在上代碼中看到,在 Base 和 Der 類的構(gòu)函數(shù)中都出現(xiàn)了設(shè)置偏移地址的操作,但是子類構(gòu)造函數(shù)會覆蓋父類的修改。這樣一來,實(shí)際的函數(shù)運(yùn)行地址依賴構(gòu)造函數(shù),子類對象被構(gòu)造就調(diào)用子類的方法,父類構(gòu)造就調(diào)用父類的方法(非純虛函數(shù)),實(shí)現(xiàn)了運(yùn)行時(shí)多態(tài)。

增加一個(gè)虛函數(shù)后, 后面的虛函數(shù)地址就添加到虛表之中,如下

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

再調(diào)用另外一個(gè)虛函數(shù)就簡單很多了,直接地址進(jìn)行偏移(這里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

簡單畫了一下虛函數(shù)運(yùn)行的內(nèi)存結(jié)構(gòu)圖

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • C++作用域與函數(shù)重載的實(shí)現(xiàn)

    C++作用域與函數(shù)重載的實(shí)現(xiàn)

    本文主要介紹了C++作用域與函數(shù)重載的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • C++ 互斥鎖原理以及實(shí)際使用介紹

    C++ 互斥鎖原理以及實(shí)際使用介紹

    本文主要聊一聊如何使用互斥鎖以及都有哪幾種方式實(shí)現(xiàn)互斥鎖。實(shí)現(xiàn)互斥,可以有以下幾種方式:互斥量(Mutex)、遞歸互斥量(Recursive Mutex)、讀寫鎖(Read-Write Lock)、條件變量(Condition Variable)。感興趣的同學(xué)可以參考一下
    2023-04-04
  • C語言實(shí)現(xiàn)字符轉(zhuǎn)unix時(shí)間戳的簡單實(shí)例

    C語言實(shí)現(xiàn)字符轉(zhuǎn)unix時(shí)間戳的簡單實(shí)例

    下面小編就為大家?guī)硪黄狢語言實(shí)現(xiàn)字符轉(zhuǎn)unix時(shí)間戳的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-06-06
  • C++ 中

    C++ 中"emplace_back" 與 "push_back" 的區(qū)別

    這篇文章主要介紹了C++ 中"emplace_back" 與 "push_back" 的區(qū)別的相關(guān)資料,需要的朋友可以參考下
    2017-04-04
  • c++中map的基本用法和嵌套用法實(shí)例分析

    c++中map的基本用法和嵌套用法實(shí)例分析

    這篇文章主要介紹了c++中map的基本用法和嵌套用法,以實(shí)例形式分析了map容器的基本使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-01-01
  • C語言選擇、循環(huán)、函數(shù)、數(shù)組與操作符

    C語言選擇、循環(huán)、函數(shù)、數(shù)組與操作符

    這篇文章主要介紹了C語言選擇、循環(huán)、函數(shù)、數(shù)組與操作符,文章基于C語言展開對主題的詳細(xì)介紹,下文內(nèi)容需要的小伙伴可以參考一下
    2022-04-04
  • C語言實(shí)現(xiàn)靜態(tài)版通訊錄的代碼分享

    C語言實(shí)現(xiàn)靜態(tài)版通訊錄的代碼分享

    這篇文章主要為大家詳細(xì)介紹了如何利用C語言實(shí)現(xiàn)一個(gè)簡單的靜態(tài)版通訊錄,主要運(yùn)用了結(jié)構(gòu)體,一維數(shù)組,函數(shù),分支與循環(huán)語句等等知識,需要的可以參考一下
    2023-01-01
  • C++實(shí)現(xiàn)LeetCode(112.二叉樹的路徑和)

    C++實(shí)現(xiàn)LeetCode(112.二叉樹的路徑和)

    這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(112.二叉樹的路徑和),本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • 一文讀懂C++中Protobuf

    一文讀懂C++中Protobuf

    Protocol Buffers 是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化、或者說序列化,本文詳解了Protobuf的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-05-05
  • Win10+VS2017新CUDA項(xiàng)目配置教程

    Win10+VS2017新CUDA項(xiàng)目配置教程

    這篇文章主要為大家詳細(xì)介紹了Win10+VS2017新CUDA項(xiàng)目配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09

最新評論