0x01虚函数分析

本文深入探讨了C++中虚函数的工作原理,通过示例详细解释了编译器如何使用虚函数表来实现多态性。当基类指针指向派生类对象时,通过虚函数表可以调用正确的函数实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虚函数工作原理:

编译器处理虚函数的方法是:给每个对象添加一个隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中

无继承时虚函数表:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void fun()
    {
        cout << "1" << endl;
    }
    virtual void foo()
    {
        cout << "2" << endl;
    }
    virtual void fcn()
    {
        cout << "3" << endl;
    }
};
int  main()
{
    Base obj;
    Base *b = &obj;
    b->fun();
    b->foo();
    b->fcn();
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

虚表单一继承:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void funA()
    {
        cout << "1" << endl;
    }
    virtual void foo()
    {
        cout << "2" << endl;
    }
    virtual void fcn()
    {
        cout << "3" << endl;
    }
};
class Derived :public Base
{
public:
    virtual void fun()
    {
        cout << "4" << endl;
    }
    virtual void foo()
    {
        cout << "5" << endl;
    }
    virtual void fcn()
    {
        cout << "6" << endl;
    }
};
int  main()
{
    Derived Dobj;
    Base *p = &Dobj;
    p->funA();
    p->foo();
    p->fcn();
    return 0;
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

<think>好的,我现在需要回答用户关于纯虚函数是否在虚函数表中的问题。首先,我得回想一下C++中纯虚函数的基本概念。纯虚函数是在基类中声明但没有实现的虚函数,它们的存在使得基类成为抽象类,无法实例化。派生类必须覆盖这些纯虚函数才能被实例化。 用户的问题是纯虚函数是否存在于虚函数表中。根据之前的知识,普通的虚函数虚函数表中有一个指向其实现的指针。那纯虚函数呢?如果派生类没有覆盖纯虚函数,会发生什么?这时候虚函数表中的条目是什么?或者当纯虚函数未被覆盖时,是否有某种默认的处理机制? 接下来,我需要考虑虚函数表的结构。每个含有虚函数的类都有一个虚函数表,其中存放着指向各个虚函数实现的指针。对于纯虚函数,基类中没有提供实现,所以在基类的虚函数表中,对应的条目应该是一个空指针或者某种占位符。但实际情况可能不同,比如编译器可能会插入一个特殊的函数,比如纯虚调用处理函数,这样在未覆盖的情况下调用会触发错误。 然后想到,如果派生类没有覆盖纯虚函数,那么它仍然是抽象类,无法实例化。此时,派生类的虚函数表中该纯虚函数的条目可能仍然指向基类的纯虚函数条目,或者某种错误处理函数。例如,某些编译器会在虚函数表中放置一个指向内部处理函数的指针,当调用未实现的纯虚函数时,会抛出异常或终止程序。 还需要考虑不同编译器的实现可能不同,但大体逻辑应该一致。比如,当尝试通过基类指针调用未实现的纯虚函数时,程序通常会崩溃或调用未实现的处理函数。比如在GCC中,可能会调用__cxa_pure_virtual这个函数,它通常会终止程序。 总结一下,纯虚函数在基类的虚函数表中确实有一个条目,但这个条目指向的不是有效的函数实现,而是编译器提供的某种占位符或错误处理函数。当派生类覆盖了纯虚函数后,虚函数表中的对应条目会被更新为派生类的实现。如果未覆盖,则条目保持指向错误处理函数,确保在调用时能够检测到未实现的情况。 另外,需要验证这一点是否正确。可以查找C++标准或权威资料,确认纯虚函数虚函数表中的表现。例如,C++标准规定,含有纯虚函数的类是抽象类,无法实例化。调用未实现的纯虚函数会导致未定义行为,但通常在运行时会有某种处理机制,比如终止程序。而虚函数表中的条目在基类中指向一个导致错误的函数,在派生类中如果实现了,则指向实际函数。 因此,回答用户的问题时,应指出纯虚函数确实存在于虚函数表中,但其条目指向一个特殊处理函数,用于在未覆盖时触发错误。同时,覆盖后的派生类虚函数表会更新该条目为实际函数地址。</think>在C++中,纯虚函数**确实存在于虚函数表中**,但其实现方式与普通虚函数有本质区别。以下是其核心机制解析: --- ### 虚函数表结构 ```cpp class AbstractBase { public: virtual void pure_func() = 0; // 纯虚函数 virtual void normal_func() { /*...*/ } // 普通虚函数 }; ``` #### 基类虚函数表布局 ``` +------------------+ | &__cxa_pure_virtual | // 纯虚函数占位符 | &AbstractBase::normal_func | +------------------+ ``` #### 派生类虚函数表布局(实现纯虚函数后) ``` class Concrete : public AbstractBase { public: void pure_func() override { /*...*/ } }; +------------------+ | &Concrete::pure_func | // 有效函数指针 | &AbstractBase::normal_func | +------------------+ ``` --- ### 关键实现原理 1. **占位符机制** - 未实现的纯虚函数在虚表中**指向特殊处理函数** - 例如GCC使用`__cxa_pure_virtual`,MSVC使用`_purecall` ```cpp // 典型处理函数实现 void __cxa_pure_virtual() { std::terminate(); // 直接终止程序 } ``` 2. **动态绑定保护** ```cpp AbstractBase* obj = new Concrete; obj->pure_func(); // 正常调用派生类实现 obj->AbstractBase::pure_func(); // 编译错误:纯虚函数不能直接调用 ``` 3. **未实现检测** ```cpp // 如果派生类未实现纯虚函数 class BadDerived : public AbstractBase {}; // 仍然是抽象类 BadDerived d; // 编译错误:无法实例化抽象类 ``` --- ### 内存布局示意图 ``` 代码段内存区域: +------------------+ | __cxa_pure_virtual | <-- 错误处理函数 | AbstractBase::normal_func | | Concrete::pure_func | <-- 实际实现 +------------------+ 虚表实例: AbstractBase虚表: [0]: 0x401000 (__cxa_pure_virtual) [1]: 0x402000 (AbstractBase::normal_func) Concrete虚表: [0]: 0x403000 (Concrete::pure_func) [1]: 0x402000 (AbstractBase::normal_func) ``` --- ### 特殊场景分析 1. **构造函数内调用纯虚函数** ```cpp class DangerousBase { public: DangerousBase() { pure_virtual(); } // 危险操作! virtual void pure_virtual() = 0; }; // 运行时调用会触发terminate() ``` 2. **析构函数调用纯虚函数** ```cpp class DangerousBase { public: virtual ~DangerousBase() { pure_virtual(); } virtual void pure_virtual() = 0; }; // 即使派生类已实现,此时对象可能已部分销毁 ``` --- ### 底层实现验证 通过反汇编观察调用过程: ```asm ; 调用未实现的纯虚函数 mov rax, [rdi] ; 加载vptr call [rax] ; 跳转到__cxa_pure_virtual ; 调用已实现的纯虚函数 mov rax, [rdi] call [rax] ; 跳转到Concrete::pure_func ``` --- ### 设计哲学解析 1. **类型系统完整性** - 保证所有虚函数都有虚表条目 - 维持统一的虚函数调用机制 2. **安全防护机制** - 通过占位符函数实现运行时保护 - 避免直接跳转到无效地址 3. **与接口设计的统一性** ```cpp // 对比Java接口 interface MyInterface { void method(); // 隐含abstract } ``` C++通过纯虚函数实现类似效果,但保留更多底层控制 --- ### 不同编译器的实现差异 | 编译器 | 纯虚函数处理函数 | 典型行为 | |--------|--------------------------|-------------------| | GCC | `__cxa_pure_virtual` | 调用std::terminate| | MSVC | `_purecall` | 触发断言失败 | | Clang | `__cxxabiv1::__pure_virtual` | 终止进程 | --- ### 总结要点 1. 纯虚函数在虚表中**必定存在对应条目** 2. 未实现的纯虚函数条目指向**错误处理函数** 3. 该机制保证: - 抽象类的实例化限制(编译期检查) - 未实现方法的运行时保护 - 虚函数调用机制的完整性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值