zoukankan      html  css  js  c++  java
  • 从汇编看c++中成员函数指针(一)

    下面先来看c++的源码:

    #include <cstdio>
    using namespace std;
    
    class X {
    public:
        int get1() {
            return 1;
        }
        virtual int get2() {
            return 2;
        }
        virtual int get3() {
            return 3;
        }
    };
    
    int main() {
        X x;
        X* xp = &x;
        int(X::*gp1)() = &X::get1;
        int(X::*gp2)() = &X::get2;
        int(X::*gp3)() = &X::get3;
        /*********************输出各个成员函数指针的值*****************/
        printf("gp1 = %lu
    ", gp1);
        printf("gp2 = %lu
    ", gp2);
        printf("gp3 = %lu
    ", gp3);
        
        /********************用成员函数指针调用虚函数*******************/
        (x.*gp1)();
        (xp->*gp1)();
        (x.*gp2)();
        (xp->*gp2)();
        (x.*gp3)();
        (xp->*gp3)();
    }

    类X有3个成员函数,其中get1是普通的成员函数,而get2和get3都分别是虚成员函数。在main函数里面分别定义了指向这三个成员函数的指针,并且将他们的值输出来。然后用成员指针对他们进行了调用。下面来看调用后的结果:

    可以看到,gp1 gp2 gp3输出的好像都是地址。下面主要来看一下面函数里面,定义三个成员函数指针的汇编码:

    ; 20   :     int(X::*gp1)() = &X::get1;
    
        mov    DWORD PTR _gp1$[ebp], OFFSET ?get1@X@@QAEHXZ ; X::get1;取?get1@X@@QAEHXZ所代表的内存地址,即X::get1的地址给gp1
    
    ; 21   :     int(X::*gp2)() = &X::get2;
    
        mov    DWORD PTR _gp2$[ebp], OFFSET ??_9X@@$BA@AE ; X::`vcall'{0}'; 取??_9X@@$BA@AE所代表的内存地址,即X::`vcall'{0}'的地址给gp2
    
    ; 22   :     int(X::*gp3)() = &X::get3;
    
        mov    DWORD PTR _gp3$[ebp], OFFSET ??_9X@@$B3AE ; X::`vcall'{4}';取??_9X@@$B3AE所代表的内存地址,即X::`vcall'{4}'的地址给gp3

    通过汇编码可以看到,gp1存储的确实是成员函数get1的地址,而gp2和gp3存储的确不是虚函数get2和get3的地址,而是X::vcall{0}和X::vcall{4}的地址。那么,X::vcall{0}和vcall{4}到底是什么呢?我们继续看汇编代码,接下来是X::vcall{0}的汇编码:

    ??_9X@@$BA@AE PROC                    ; X::`vcall'{0}', COMDAT
        mov    eax, DWORD PTR [ecx];寄存是ecx里面保存的是对象x的首地址,
                                ;因此,这里是将对象x首地址处内存内容(即vftable首地址)给寄存器eax
        jmp    DWORD PTR [eax];跳转到vftable首地址处内存(里面存的是虚函数get2的地址)所存储的地址处执行
                           ;这里就是跳转去执行虚函数get2
    ??_9X@@$BA@AE ENDP                    ; X::`vcall'{0}'

    通过汇编码,我们可以发现,X::vcall{0}是一段代码,它的作用是跳转到相应的虚函数地址去执行。

    下面是X::vcall{4}的汇编码:

    ??_9X@@$B3AE PROC                    ; X::`vcall'{4}', COMDAT
        mov    eax, DWORD PTR [ecx];寄存器ecx里面存储的是对象x的首地址
                                  ;因此,这里是将对象x首地址处内存内容(即vftable首地址)给寄存器eax
        jmp    DWORD PTR [eax+4];跳转到偏移vftable首地址处4byte处内存(里面存的是虚函数get2的地址)所存储的地址处执行
                           ;这里就是跳转去执行虚函数get3
    ??_9X@@$B3AE ENDP                    ; X::`vcall'{4}'

    通过汇编码,我们返现,X::vcall{4}和X::vcall{0}的作用一样。

    因此,gp2和gp3存储的不是虚函数get2和虚函数get3的地址,而是相应的vcall函数的地址。那么,通过这些成员函数的指针调用函数的时候,有发生了什么?

    下面是调用函数的汇编代码:

    ; 28   :     /********************用成员函数指针调用虚函数*******************/
    ; 29   :     (x.*gp1)();
    
        lea    ecx, DWORD PTR _x$[ebp];取对象x的首地址,给寄存器ecx,作为遗憾参数传递给get1
        call    DWORD PTR _gp1$[ebp];gp1中存有get1的地址,这里直接调用get1函数
    
    ; 30   :     (xp->*gp1)();
    
        mov    ecx, DWORD PTR _xp$[ebp];指针变量xp保存有对象x的首地址,这里将对象首地址给寄存器ecx 作为隐含参数传递给get1
        call    DWORD PTR _gp1$[ebp];gp1中存有get1的地址,这里直接调用get1函数
                                    ;用指针和对象操作成员变量指针效果一样
    
    ; 31   :     (x.*gp2)();
    
        lea    ecx, DWORD PTR _x$[ebp];取对象x的首地址,给寄存器ecx,作为隐含参数传递给X::vcall{0}
        call    DWORD PTR _gp2$[ebp];gp2中存有X::vcall{0}的地址,这里调用X::vcall{0},由vcall{0}查询虚表,执行get2
    
    ; 32   :     (xp->*gp2)();
    
        mov    ecx, DWORD PTR _xp$[ebp];用指针调用和用对象x调用效果一样
        call    DWORD PTR _gp2$[ebp]
    
    ; 33   :     (x.*gp3)();
    
        lea    ecx, DWORD PTR _x$[ebp];将对象x首地址给寄存器ecx,作为隐含参数传递给X::vcall{4}
        call    DWORD PTR _gp3$[ebp];gp3中存有X::vcall{4}的地址,这里调用X::vcall{4},由vcall{4}查询虚表,调用get3
    
    ; 34   :     (xp->*gp3)();
    
        mov    ecx, DWORD PTR _xp$[ebp];指针调用和对象调用效果一样
        call    DWORD PTR _gp3$[ebp]

    通过汇编码发现,普通成员函数时通过地址直接调用,而虚成员函数时通先调用vcall函数,然后由vcall函数查询虚表调用相应的虚函数

    由此可以看出,一个类里面的每一给虚函数都有一个vcall函数与之对应,通过vcall函数来调用相应的虚函数。

  • 相关阅读:
    Centos7更改网卡名为eth0
    Centos7部署Open-Falcon监控
    centos6.x一键15项系统优化(转自努力哥)
    运维题目(十三)
    运维题目(十二)
    Mongodb的学习整理(下)
    Centos7下yum安装mongodb
    浏览器缓存
    控制反转
    js setTimeOut()
  • 原文地址:https://www.cnblogs.com/chaoguo1234/p/3175479.html
Copyright © 2011-2022 走看看