c++ "pure Virtual function called" 引起的crash详解

本实例即为经典的讲解C++继承、虚函数、运行时多态的实例。今天我们再用它作为讲解"pure virtual functioncalled"的实例。(在某些平台上也可能输出"pure virtual methodcalled")

  1. 实现基类Shape的area函数
 file:testVirtualFunc.cpp

#include <stdio.h>
#include <stdlib.h>
 
#define PI 3.1415926
 
class Shape
{
private:
    double ValuePerSquareUnit;
 
protected:
    Shape(double valuePerSquareUnit):
        ValuePerSquareUnit(valuePerSquareUnit)
    {
    }
 
public:
    virtual double area() const = 0;
 
    double value() const
    {
        return ValuePerSquareUnit * area();
    }
 
    virtual ~Shape()
    {
        printf("Shape::~Shape() is called");
    }
 
    double getPerSquareUnit()
    {
        return ValuePerSquareUnit;
    }
};
 
class Rectangle : public Shape
{
private:
    double Width;
    double Height;
 
public:
    Rectangle(double width, double height, double valuePerSquareUnit):
        Shape(valuePerSquareUnit),Width(width),Height(height)
    {
    }
 
    virtual ~Rectangle()
    {
    }
 
    virtual double area() const
    {
        return Width * Height;
    }
 
};
 
class Circle: public Shape
{
    double Radius;
 
public:
    Circle(double radius, double valuePerSquareUnit):
        Shape(valuePerSquareUnit),Radius(radius)
    {
    }
 
    virtual ~Circle()
    {
    }
 
    virtual double area() const
    {
        return PI * Radius * Radius;
    }
};
 
int main()
{
    Rectangle* pr = new Rectangle(30, 20, 10);
    Circle* pc = new Circle(15, 10);
 
    //invoke Rectangle::area()
    printf("rectangle: area = %.2f, PerSquareUnit = %.2f, value = %.2f\n", 
                pr->area(), pr->getPerSquareUnit(), pr->value());
    //invoke Circle::area()
    printf("circle   : area = %.2f, PerSquareUnit = %.2f, value = %.2f\n", 
                pc->area(), pc->getPerSquareUnit(), pc->value());
    
    Shape* shape;
    shape = pr;
    printf("rectangle: area = %.2f, PerSquareUnit = %.2f, value = %.2f\n", 
                shape->area(), shape->getPerSquareUnit(), shape->value());
 
    shape = pc;
    printf("circle   : area = %.2f, PerSquareUnit = %.2f, value = %.2f\n", 
                shape->area(), shape->getPerSquareUnit(), shape->value());
 
    return 0;
}

编译一下看看。

# g++ -o testVirtualFunc testVirtualFunc.cpp 
/tmp/ccmGHV0C.o: In function `Shape::Shape(double)':
testVirtualFunc.cpp:(.text._ZN5ShapeC2Ed[Shape::Shape(double)]+0x13): undefined reference to `vtable for Shape'
/tmp/ccmGHV0C.o: In function `Shape::~Shape()':
testVirtualFunc.cpp:(.text._ZN5ShapeD2Ev[Shape::~Shape()]+0x7): undefined reference to `vtable for Shape'
/tmp/ccmGHV0C.o:(.rodata._ZTI9Rectangle[typeinfo for Rectangle]+0x8): undefined reference to `typeinfo for Shape'
/tmp/ccmGHV0C.o:(.rodata._ZTI6Circle[typeinfo for Circle]+0x8): undefined reference to `typeinfo for Shape'
collect2: ld returned 1 exit status

实现基类Shape的area函数即可消除该编译错误。

即将以下area函数

virtual double area() const;

改为

virtual double area() const
{
}

输出结果如下。

# g++ -o testVirtualFunc testVirtualFunc.cpp 
# ./testVirtualFunc
rectangle: area = 600.00, PerSquareUnit = 10.00, value = 6000.00
circle   : area = 706.86, PerSquareUnit = 10.00, value = 7068.58
rectangle: area = 600.00, PerSquareUnit = 10.00, value = 6000.00
circle   : area = 706.86, PerSquareUnit = 10.00, value = 7068.58
  1. 将基类Shape定义为纯虚类

即将以下area函数

virtual double area() const;

改为

virtual double area() const = 0;

也可以实现该功能,其输出结果与上相同。

  1. 对该纯虚类的程序修改

3.1 在Shape类的构造函数中直接调用纯虚函数

修改Shape的构造函数如下。

Shape(double valuePerSquareUnit):
    ValuePerSquareUnit(valuePerSquareUnit)
{
    std::cout << "creating shape, area = " << area() << std::endl;
}

则会在编译时出现如下错误:

# g++ -o testPureVirtualFunc testPureVirtualFunc.cpp
testPureVirtualFunc.cpp: In constructor ‘Shape::Shape(double)’:
testPureVirtualFunc.cpp:18: warning: abstract virtual ‘virtual double Shape::area() const’ called from constructor
/tmp/cc2WbWlw.o: In function `Shape::Shape(double)':
testPureVirtualFunc.cpp:(.text._ZN5ShapeC2Ed[Shape::Shape(double)]+0x2c): undefined reference to `Shape::area() const'
collect2: ld returned 1 exit status

即不能在构造函数中直接调用纯虚函数。

(Meyers, 3rd edition, Item 9: "Never call virtual functionsduring construction or destruction.") --in the third edition of Scott Meyers's "Effective C++"

3.2 在Shape类的构造函数中间接调用纯虚函数

修改Shape的构造函数如下。

Shape(double valuePerSquareUnit):
    ValuePerSquareUnit(valuePerSquareUnit)
{
    //std::cout << "creating shape, area = " << area() << std::endl;
    std::cout << "creating shape, value = " << value() << std::endl;
}

其执行结果如下。

# g++ -o testPureVirtualFunc testPureVirtualFunc.cpp
# ./testPureVirtualFunc
pure virtual method called
terminate called without an active exception
Aborted (core dumped)

进入core dump文件调试,可以发现,是在value函数中调用area时出现该问题的。

(gdb) bt
#0  0x00110402 in __kernel_vsyscall ()
#1  0x00a8e690 in raise () from /lib/libc.so.6
#2  0x00a8ff91 in abort () from /lib/libc.so.6
#3  0x06894ba0 in __gnu_cxx::__verbose_terminate_handler () from /usr/lib/libstdc++.so.6
#4  0x06892685 in ?? () from /usr/lib/libstdc++.so.6
#5  0x068926c2 in std::terminate () from /usr/lib/libstdc++.so.6
#6  0x06892d65 in __cxa_pure_virtual () from /usr/lib/libstdc++.so.6
#7  0x08048b00 in Shape::value (this=0x8261008) at testPureVirtualFunc.cpp:27
#8  0x08048cd0 in Shape (this=0x8261008, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:19
#9  0x08048d44 in Rectangle (this=0x8261008, width=30, height=20, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:49
#10 0x08048920 in main () at testPureVirtualFunc.cpp:87

3.3 为什么会出现"pure virtual functincalled"

要想搞清楚这个问题,要思考这两件事情:

a. 具体是在什么时刻调用了纯虚函数?

b. 是谁打印的"pure virtual functioncalled"?

带着这两个问题,我们要深入调用内部,找到根本原因。调试过程如下。

注意,编译时要加上"-g"选项,为调试准备符号。

# g++ -g -o testPureVirtualFunc testPureVirtualFunc.cpp

# gdb ./testPureVirtualFunc
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...
Using host libthread_db library "/lib/libthread_db.so.1".
(gdb) l
78          virtual double area() const
79          {
80              return PI * Radius * Radius;
81          }
82      };
83
84
85      int main()
86      {
87          Rectangle* pr = new Rectangle(30, 20, 10);
(gdb) b 87
Breakpoint 1 at 0x80488e8: file testPureVirtualFunc.cpp, line 87.
(gdb) r
Starting program: /home/abo/linux/2009-12-28 testPureVirtualFuncCalled/testPureVirtualFunc 

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ac/2eeb206486bb7315d6ac4cd64de0cb50838ff6.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/88/27308433e33aeefb560f42fb133577c8936f20.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/92/8ab51a53627c59877a85dd9afecc1619ca866c.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/db/8cb95645d5df469d4aece301cdb5e60087be21.debug

warning: Missing the separate debug info file: /usr/lib/debug/.build-id/ba/4ea1118691c826426e9410cafb798f25cefad5.debug

Breakpoint 1, main () at testPureVirtualFunc.cpp:87
87          Rectangle* pr = new Rectangle(30, 20, 10);
(gdb) step
Rectangle (this=0x909d008, width=30, height=20, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:49
49              Shape(valuePerSquareUnit),Width(width),Height(height)
(gdb) p this
$1 = (Rectangle * const) 0x909d008
(gdb) p *this
$2 = {<Shape> = {_vptr.Shape = 0x0, ValuePerSquareUnit = 0}, Width = 0, Height = 0}
(gdb) info line Rectangle
Line 42 of "testPureVirtualFunc.cpp" is at address 0x8048d08 <Rectangle> but contains no code.
(gdb) step
Shape (this=0x909d008, valuePerSquareUnit=10) at testPureVirtualFunc.cpp:16
16              ValuePerSquareUnit(valuePerSquareUnit)
(gdb) p this
$3 = (Shape * const) 0x909d008
(gdb) p *this
$4 = {_vptr.Shape = 0x0, ValuePerSquareUnit = 0}
(gdb) x/4 0x909d008
0x909d008:      0x00000000      0x00000000      0x00000000      0x00000000
(gdb) step
19              std::cout << "creating shape, value = " << value() << std::endl;
(gdb) x/4 0x909d008
0x909d008:      0x08048fa8      0x00000000      0x40240000      0x00000000
(gdb) p sizeof(ValuePerSquareUnit)
$5 = 8
(gdb) p sizeof(*this)
$6 = 12
(gdb) p *this
$7 = {_vptr.Shape = 0x8048fa8, ValuePerSquareUnit = 10}
(gdb) x/15i 0x8048fa8
0x8048fa8 <_ZTV5Shape+8>:       xor    %al,0x8c6e0804(%edi)
0x8048fae <_ZTV5Shape+14>:      add    $0x8,%al
0x8048fb0 <_ZTV5Shape+16>:      cmp    $0x8c,%al
0x8048fb2 <_ZTV5Shape+18>:      add    $0x8,%al
0x8048fb4:      add    %al,(%eax)
0x8048fb6:      add    %al,(%eax)
0x8048fb8 <_ZTV6Circle>:        add    %al,(%eax)
0x8048fba <_ZTV6Circle+2>:      add    %al,(%eax)
0x8048fbc <_ZTV6Circle+4>:      int3   
0x8048fbd <_ZTV6Circle+5>:      popl   (%eax,%ecx,1)
0x8048fc0 <_ZTV6Circle+8>:      mov    %es:(%eax,%ecx,1),%eax
0x8048fc4 <_ZTV6Circle+12>:     or     0x48bd808(%esp,%eax,1),%cl
0x8048fcb <_ZTV6Circle+19>:     or     %ch,(%eax)
0x8048fcd <_ZTI6Circle+1>:      movsl  %ds:(%esi),%es:(%edi)
0x8048fce <_ZTI6Circle+2>:      add    $0x8,%al
(gdb) step
Shape::value (this=0x909d008) at testPureVirtualFunc.cpp:27
27              return ValuePerSquareUnit * area();
(gdb) x/4 0x909d008
0x909d008:      test   $0x8f,%al
0x909d00a:      add    $0x8,%al
0x909d00c:      add    %al,(%eax)
0x909d00e:      add    %al,(%eax)
(gdb) x/4w 0x909d008
0x909d008:      0x08048fa8      0x00000000      0x40240000      0x00000000
(gdb) step
pure virtual method called
terminate called without an active exception

Program received signal SIGABRT, Aborted.
0x00110402 in __kernel_vsyscall ()
(gdb) 

我们可以通过C++filt命令对那些看不太懂的符号进行解析,

#c++filt _ZTV5Shape
vtablefor Shape
#c++filt _ZTV6Circle
vtablefor Circle

具体的解释,我们可以查看c++filt的man手册页。

通过strace命令,我们可以清楚地看到程序在core dump之前调用write在标准输出中打印出"pure virtual method called",如下。

# ./testPureVirtualFunc
pure virtual method called
terminate called without an active exception
Aborted (core dumped)
# strace ./testPureVirtualFunc

其部分strace结果如下。

mmap2(0xa65000, 1410608, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xa65000
mmap2(0xbb8000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x153) = 0xbb8000
mmap2(0xbbb000, 9776, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xbbb000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7f28000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7f28af0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xbb8000, 8192, PROT_READ)     = 0
mprotect(0xbe7000, 4096, PROT_READ)     = 0
mprotect(0x68be000, 16384, PROT_READ)   = 0
mprotect(0xa61000, 4096, PROT_READ)     = 0
munmap(0xb7f29000, 71683)               = 0
brk(0)                                  = 0x8410000
brk(0x8431000)                          = 0x8431000
write(2, "pure virtual method called\n", 27pure virtual method called
) = 27
write(2, "terminate called without an acti"..., 45terminate called without an active exception
) = 45
rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0
gettid()                                = 25398
tgkill(25398, 25398, SIGABRT)           = 0
--- SIGABRT (Aborted) @ 0 (0) ---
+++ killed by SIGABRT (core dumped) +++

参考:
https://www.artima.com/cppsource/pure_virtual.html

https://blog.csdn.net/livelylittlefish/article/details/9750593

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容

  • 一:基本定义 多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。 C+...
    欧阳杰_fac9阅读 1,072评论 0 0
  • 技术交流QQ群:1027579432,欢迎你的加入! 1.Cpp中的多态 多态按字面的意思就是多种形态。当类之间存...
    CurryCoder阅读 671评论 0 3
  • 一个博客,这个博客记录了他读这本书的笔记,总结得不错。《深度探索C++对象模型》笔记汇总 1. C++对象模型与内...
    Mr希灵阅读 5,584评论 0 13
  • C++类和对象 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心...
    863cda997e42阅读 652评论 0 4
  • 1.面向对象的程序设计思想是什么? 答:把数据结构和对数据结构进行操作的方法封装形成一个个的对象。 2.什么是类?...
    少帅yangjie阅读 5,000评论 0 14