汇编视角分析C++虚函数实现原理

本文详细分析了C++虚函数的实现原理,通过ARM32架构下G++编译器的汇编代码,探讨了虚函数表的构建和虚函数调用流程。在AudioState类的构造函数中,可以看到虚函数表的建立,并且在对象创建时,vptr被初始化为虚函数表的地址,使得通过vptr可以找到并调用相应的虚函数。在main函数中,展示了如何通过vptr找到虚函数地址并进行调用的过程。
摘要由CSDN通过智能技术生成

1.概述

        虚函数是c++语言非常重要的机制,日常的c++编程工作中经常使用虚函数,通过汇编视角来探究虚函数的实现原理,有助于深刻理解虚函数的内部机制。尤其要说明的是:c++语法规范并没有规定虚函数的具体实现方案,不同的编译器实现方式可以不同,本文基于arm32平台,g++编译器来分析虚函数表的实现机制,基本主流编译器的原理基本是相似的,所以理解g++实现方案再去分析其他编译器的实现也是类似的。

2.示例代码

class State {
public:
    State(int id) { 
        this->mId = id;
    }
    
    State(const State &state) {
        printf("State copy constructor\n");
    }
    
    virtual void dump() {
        printf("State mId:%d\n", mId);
    }
                                                                                                                                                           
public:
    int mId;
};

class AudioState : public State {
public:
    AudioState(int id) : State(id) {
    }

    void dump() {
        printf("AudioState mId:%d\n", mId); 
    }
};
int main(){
    printf("%d\n" ,sizeof(AudioState));
    State *ps = new AudioState(1);
    ps->dump();
    return 0;
}

代码编译和反汇编:

arm-linux-androideabi-g++ -pie -fPIE -o vtable vtable.cpp

arm-linux-androideabi-objdump vtable > vtable.txt

3. 虚函数调用实现基本原理

1. 如果是通过类指针调用相应的虚函数,通过vtable实现调用。

2. 如果子类重新实现了虚函数,vtable中对应项填写子类的函数(如上图),否则,填写父类德函数。

如下代码:

AudioState *ps = new AudioState(1);
ps->dump();

调用dump函数的时候,首先从AudioState对象的vptr指针拿到虚函数表(vtable),然后获取到虚函数表中dump函数对应的表项,所以最终调用到AudioState::dump函数。下面通过g++ arm汇编分析上述调用过程,帮助我们更深刻的理解虚函数实现原理。

4.虚函数调用实现

1. AudioState虚函数表在哪里构建?

答:AudioState构造函数里面

下面分析AudioState的汇编代码:

000014b0 <_ZN10AudioStateC1Ei>:
    14b0:   e92d4810    push    {r4, fp, lr}
    14b4:   e28db008    add fp, sp, #8
    14b8:   e24dd00c    sub sp, sp, #12

    //fp-16栈处存储对象指针,即对象首地址
    14bc:   e50b0010    str r0, [fp, #-16]

    //fp-20栈处存储构造函数的参数,即id参数
    14c0:   e50b1014    str r1, [fp, #-20]  ; 0xffffffec

    //以下两行将r4 = 10abc(1500地址处的值) + 14d0 = 11F8c
    14c4:   e59f4034    ldr r4, [pc, #52]   ; 1500 <_ZN10AudioStateC1Ei+0x50>
    14c8:   e08f4004    add r4, pc, r4

    14cc:   e51b3010    ldr r3, [fp, #-16]
    14d0:   e1a00003    mov r0, r3
    14d4:   e51b1014    ldr r1, [fp, #-20]  ; 0xffffffec
    14d8:   ebffffd0    bl  1420 <_ZN5StateC1Ei>

    //r3存储AudioState对象的首地址
    14dc:   e51b3010    ldr r3, [fp, #-16]

    //r2从如下地址取值:ffffffa4(1504处的值, -92的补码) + r4= 11F8c - (5c) = 11F30
    //r2 = [11F30] = 11c68, 11c68就是AudioState虚函数表
    14e0:   e59f201c    ldr r2, [pc, #28]   ; 1504 <_ZN10AudioStateC1Ei+0x54>
    14e4:   e7942002    ldr r2, [r4, r2]

    //r2 取AudioState虚函数表的第三项(前面存储了typeinfo相关项), r2 = 11c70
    14e8:   e2822008    add r2, r2, #8

    //将r2 11c70这个地址存储对象的首地址,即vptr = 11c70
    14ec:   e5832000    str r2, [r3]
    14f0:   e51b3010    ldr r3, [fp, #-16]
    14f4:   e1a00003    mov r0, r3
    14f8:   e24bd008    sub sp, fp, #8
    14fc:   e8bd8810    pop {r4, fp, pc}
    1500:   00010abc            ; <UNDEFINED> instruction: 0x00010abc
    1504:   ffffffa4            ; <UNDEFINED> instruction: 0xffffffa4

00011c68 <_ZTV10AudioState>:                                                                                                                               
   11c68:   00000000    andeq   r0, r0, r0
   11c6c:   00011c84    andeq   r1, r1, r4, lsl #25
   11c70:   00001508    andeq   r1, r0, r8, lsl #10    //1508即AudioState::dump
   11c74:   00000000    andeq   r0, r0, r0

00001508 <_ZN10AudioState4dumpEv>:
    1508:   e92d4800    push    {fp, lr}
    150c:   e28db004    add fp, sp, #4
    1510:   e24dd008    sub sp, sp, #8
    1514:   e50b0008    str r0, [fp, #-8]
    1518:   e51b3008    ldr r3, [fp, #-8]
    151c:   e5933004    ldr r3, [r3, #4]
    1520:   e59f2014    ldr r2, [pc, #20]   ; 153c <_ZN10AudioState4dumpEv+0x34>
    1524:   e08f2002    add r2, pc, r2
    1528:   e1a00002    mov r0, r2
    152c:   e1a01003    mov r1, r3
    1530:   ebffff14    bl  1188 <printf@plt>
    1534:   e24bd004    sub sp, fp, #4
    1538:   e8bd8800    pop {fp, pc}
    153c:   0000d434    andeq   sp, r0, r4, lsr r4

根据上面汇编代码14ec行,最终将r2(11c70)存入r3中指向地址,其中r2时虚函数表的地址,对应的表项内容:1508,对应的是AudioState::dump函数地址,说明g++编译器会把vptr放在对象的开端处。

2. 虚函数调用流程?

答:通过vptr找到对应的虚函数

main函数的汇编代码:

000013d4 <main>:
    13d4:   e92d4810    push    {r4, fp, lr}
    13d8:   e28db008    add fp, sp, #8
    13dc:   e24dd00c    sub sp, sp, #12
    13e0:   e3a00008    mov r0, #8
    13e4:   fa000094    blx 163c <_Znwj>
    13e8:   e1a04000    mov r4, r0
    13ec:   e1a00004    mov r0, r4
    13f0:   e3a01001    mov r1, #1
    13f4:   eb00002d    bl  14b0 <_ZN10AudioStateC1Ei>

    //将AudioState对象指针ps存储fp-16处
    13f8:   e50b4010    str r4, [fp, #-16]

    //ps指针读入r3
    13fc:   e51b3010    ldr r3, [fp, #-16]

    //vptr读入r3
    1400:   e5933000    ldr r3, [r3]

    //AudioState::dump虚函数地址读入r3,即1508
    1404:   e5933000    ldr r3, [r3]
    1408:   e51b0010    ldr r0, [fp, #-16]

    //跳转到dump函数执行
    140c:   e12fff33    blx r3
    1410:   e3a03000    mov r3, #0
    1414:   e1a00003    mov r0, r3
    1418:   e24bd008    sub sp, fp, #8
    141c:   e8bd8810    pop {r4, fp, pc}

注意:如果直接使用类对象调用虚函数,不会通过虚函数,汇编代码中可以看到回直接bl跳转相应对象的函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值