C++通过派生类指针调用成员函数触发Segmentation fault原因
段错误触发原因
核心问题是非法向下转型导致d为空指针,调用虚函数时触发非法内存访问,具体逻辑拆解如下:
转型操作的合法性问题
Base *b = dynamic_cast<Base *>(new Derived());是合法的向上转型:Derived公有继承自Base,Derived实例天然包含完整的Base子对象,向上类型转换是安全的,因此b是指向有效对象的合法指针,调用b->method1()时动态绑定到Derived重写的版本,运行正常。Derived *d = dynamic_cast<Derived *>(new Base());是非法的向下转型:你在堆上创建的实际对象是Base类型,内存布局中不存在Derived类扩展的内容,不满足向下转型“指针实际指向目标类型或其子类实例”的前提。dynamic_cast对指针类型做转换时,一旦判定转型不合法会直接返回nullptr(空指针),因此这里拿到的d根本不指向有效Derived对象,是空指针。
两个成员函数调用的行为差异
两个调用表现不同,是由C++对虚函数、非虚函数的绑定规则差异导致的:
d->method2()没有崩溃属于巧合,不代表代码合法:method2()是非虚成员函数,C++对非虚函数采用静态绑定,编译阶段就已经确定了函数的调用地址,调用时不会访问d指向的对象内存。编译器会把这个调用改写为类似Derived::method2(d)的形式(把d作为隐式this参数传入),由于你的method2()实现里没有访问任何对象成员,根本没用到传入的this指针,所以哪怕d是空指针也不会触发内存错误——这本质是未定义行为,换个编译选项或者在函数里加个成员变量访问立刻就会崩溃。d->method1()触发段错误是虚函数动态绑定的必然结果:method1()是虚函数,虚函数调用采用动态绑定,运行时需要先通过对象指针d,读取对象内存起始位置存储的虚表指针(vptr),再通过vptr找到对应类的虚函数表,从表中取出method1的实际入口地址完成调用。这时候d是空指针,读取d指向的低地址内存(通常是0x0附近的受操作系统保护的不可读内存)时,会被操作系统直接判定为非法内存访问,抛出段错误。
补充:哪怕你绕过
dynamic_cast用C风格强转(Derived*)new Base()拿到d,调用method1()依然会出错:此时d指向的是纯Base对象,它的vptr指向Base类的虚函数表,根本不存在Derived类重写method1后的正确表项,动态绑定时读取虚表条目会拿到非法地址,照样触发内存错误。
内容的提问来源于stack exchange,提问作者Aadamkhorbilli
相关产品推荐
相关产品推荐

