zoukankan      html  css  js  c++  java
  • Linux Debugging(四): 使用GDB来理解C++ 对象的内存布局(多重继承,虚继承)

          前一段时间再次拜读《Inside the C++ Object Model》 深入探索C++对象模型,有了进一步的理解,因此我也写了四篇博文算是读书笔记:

    Program Transformation Semantics (程序转换语义学)

    The Semantics of Copy Constructors(拷贝构造函数之编译背后的行为)

    The Semantics of Constructors: The Default Constructor (默认构造函数什么时候会被创建出来)

    The Semantics of Data: Data语义学 深入探索C++对象模型

         这些文章都获得了很大的浏览量,虽然类似的博文原来都有,可能不容易被现在仍活跃在CSDN Blog的各位同仁看到吧。因此萌生了接着将这这本书读完的同时,再接着谈一下我的理解,或者说读书笔记。

         关于C++虚函数,很多博文从各个角度来探究虚函数是如何实现的,或者说编译器是如何实现虚函数的。比较经典的文章有陈皓先生的《C++虚函数表解析》和《C++对象内存布局》。本文通过GDB来从另外一个角度来理解C++ object的内存布局,一来熟悉语言背后编译器为了实现语言特性为我们做了什么;二来熟悉使用GDB来调试程序。

          同时,本文也将对如何更好的理解C++语言提供了一个方法:使用GDB,可以很直观的理解编译器的实现,从根本上掌握C++!我们不单单只会开车,还应该知道车的内部的构造。

    2、带有虚函数的单一继承

    class Parent
    {
    public:
      Parent():numInParent(1111)
      {}
      virtual void Foo(){
      };
      virtual void Boo(){
      };
    private:
      int numInParent;
    };
    
    class Child: public Parent
    {
    public:
      Child():numInChild(2222){}
      virtual void Foo(){
      }
      int numInChild;
    };
    
    
    编译时不要忘记-g,使得gdb可以把各个地址映射成函数名。

    (gdb) set p obj on
    (gdb) p *this
    $2 = (Child) {<Parent> = {_vptr.Parent = 0x400a30, numInParent = 1111}, numInChild = 2222}
    (gdb) set p pretty on
    (gdb) p *this
    $3 = (Child) {
      <Parent> = {
        _vptr.Parent = 0x400a30,
        numInParent = 1111
      },
      members of Child:
      numInChild = 2222
    }
    (gdb)  p /a (*(void ***)this)[0]@3
    $4 = {0x4008ec <Child::Foo()>, 0x4008b4 <Parent::Boo()>, 0x6010b0 <_ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1.3+16>}
    
    解释一下gdb的命令:

    set p obj <on/off>: 在C++中,如果一个对象指针指向其派生类,如果打开这个选项,GDB会自动按照虚方法调用的规则显示输出,如果关闭这个选项的话,GDB就不管虚函数表了。这个选项默认是off。 使用show print object查看对象选项的设置。

    set p pertty <on/off>: 按照层次打印结构体。可以从设置前后看到这个区别。on的确更容易阅读。

    p /a (*(void ***)this)[0]@3
    就是打印虚函数表了。因为知道是两个,可以仅仅打印2个元素。为了知道下一个存储了什么信息,我们打印了3个值。实际上后几个元素存储了Parent 和Child的typeinfo name和typeinfo。

    总结:

    对于单一继承,

    1. vptr存储到了object的开始。

    2. 在vptr之后,从Parent开始的data member按照声明顺序依次存储。

    3. 多重继承,包含有相同的父类

    对应的C++codes:

    class Point2d{
    public:
      virtual void Foo(){}
      virtual void Boo(){}
      virtual void non_overwrite(){}
    protected:
      float _x, _y;
    };
    
    class Vertex: public  Point2d{
    public:
      virtual void Foo(){}
      virtual void BooVer(){}
    protected:
      Vertex *next;
    };
    
    class Point3d: public Point2d{
    public:
      virtual void Boo3d(){}
    protected:
      float _z;
    };
    
    class Vertex3d: public Vertex, public Point3d{
    public:
      void test(){}
    protected:
      float mumble;
    };
    
    使用GDB打印的对象内存布局:

     <Vertex> = {
        <Point2d> = {
          _vptr.Point2d = 0x400ab0,
          _x = 5.88090213e-39,
          _y = 0
        },
        members of Vertex:
        next = 0x0
      },
      <Point3d> = {
        <Point2d> = {
          _vptr.Point2d = 0x400ae0,
          _x = -nan(0x7fe180),
          _y = 4.59163468e-41
        },
        members of Point3d:
        _z = 0
      },
      members of Vertex3d:
      mumble = 0
    }
    
    可见v3d有两个vptr,指向不同的vtable。首先看一下第一个:


    (gdb) p /a (*(void ***)this)[0]@5
    $9 =   {0x4008be <Vertex::Foo()>,
      0x4008aa <Point2d::Boo()>,
      0x4008b4 <Point2d::non_overwrite()>,
      0x4008c8 <Vertex::BooVer()>,
      0xffffffffffffffe8}
    (gdb) p /a (*(void ***)this)[0]@6
    $10 =   {0x4008be <Vertex::Foo()>,
      0x4008aa <Point2d::Boo()>,
      0x4008b4 <Point2d::non_overwrite()>,
      0x4008c8 <Vertex::BooVer()>,
      0xffffffffffffffe8,
      0x400b00 <_ZTI8Vertex3d>}
    (gdb) info addr _ZTI8Vertex3d
    Symbol "typeinfo for Vertex3d" is at 0x400b00 in a file compiled without debugging.
    

    你可以注意到了,vtable打印分行了,可以使用 set p array on将打印的数组分行,以逗号结尾。

    注意到该虚函数表以

    0xffffffffffffffe8
    结尾。在单一继承中是没有这个结束标识的。

    接着看第二个vtable:

    (gdb) p /a (*(void ***)this)[1]@5
    $11 =   {0x4008b2 <Point2d::Boo()>,
      0x4008bc <Point2d::non_overwrite()>,
      0x4008d0 <Vertex::BooVer()>,
      0xffffffffffffffe8,
      0x400b00 <_ZTI8Vertex3d>}
    (gdb) info addr _ZTI8Vertex3d
    Symbol "typeinfo for Vertex3d" is at 0x400b00 in a file compiled without debugging.

    当然这个只是为了举个例子。现实中很少有人这么干吧。比如访问Foo,下面的code将会导致歧义性错误:

    v3d.Boo();
     error: request for member Boo is ambiguous
    multiInheritance.cpp:8: error: candidates are: virtual void Point2d::Boo()
    只能指定具体的subobject才能进行具体调用:

    v3d.::Vertex::Boo();

    4. 虚拟继承

    C++ codes:

    class Point2d{
    public:
      virtual void Foo(){}
      virtual void Boo(){}
      virtual void non_overwrite(){}
    protected:
      float _x, _y;
    };
    
    class Vertex: public virtual  Point2d{
    public:
      virtual void Foo(){}
      virtual void BooVer(){}
    protected:
      Vertex *next;
    };
    
    class Point3d: public virtual Point2d{
    public:
      virtual void Boo3d(){}
    protected:
      float _z;
    };
    
    class Vertex3d: public Vertex, public Point3d{
    public:
      void test(){}
    protected:
      float mumble;
    };
    
    继承关系图:


    使用gdb打印object的内存布局:

    (gdb) p *this
    $10 = (Vertex3d) {
      <Vertex> = {
        <Point2d> = {
          _vptr.Point2d = 0x400b70,
          _x = 0,
          _y = 0
        },
        members of Vertex:
        _vptr.Vertex = 0x400b18,
        next = 0x4009c0
      },
      <Point3d> = {
        members of Point3d:
        _vptr.Point3d = 0x400b40,
        _z = 5.87993804e-39
      },
      members of Vertex3d:
      mumble =  0
    }
    

    gdb打印的vptr相关:

    (gdb) p /a (*(void ***)this)[0]@60
    $25 =   {0x400870 <Vertex::Foo()>,
      0x40087a <Vertex::BooVer()>,
      0x10,
      0xfffffffffffffff0,
      0x400c80 <_ZTI8Vertex3d>, #"typeinfo for Vertex3d"
      0x400884 <Point3d::Boo3d()>,
      0x0,
      0x0,
      0xffffffffffffffe0,
      0xffffffffffffffe0, 
      0x400c80 <_ZTI8Vertex3d>, #"typeinfo for Vertex3d"
      0x400866 <_ZTv0_n24_N6Vertex3FooEv>, #"virtual thunk to Vertex::Foo()"
      0x400852 <Point2d::Boo()>,
      0x40085c <Point2d::non_overwrite()>,
      0x0,
      0x0,
      0x0,
      0x20,
      0x0,
      0x400cc0 <_ZTI6Vertex>, #"typeinfo for Vertex"
      0x400870 <Vertex::Foo()>,
      0x40087a <Vertex::BooVer()>,
      0x0,
      0x0,
      0xffffffffffffffe0,
      0xffffffffffffffe0,
      0x400cc0 <_ZTI6Vertex>, #"typeinfo for Vertex"
      0x400866 <_ZTv0_n24_N6Vertex3FooEv>, #"virtual thunk to Vertex::Foo()"
      0x400852 <Point2d::Boo()>,
      0x40085c <Point2d::non_overwrite()>,
      0x0,
      0x0,
      0x0,
      0x10,
      0x0,
      0x400d00 <_ZTI7Point3d>, #"typeinfo for Point3d"
      0x400884 <Point3d::Boo3d()>,
      0x0,
      0x0,
      0x0,
      0xfffffffffffffff0,
      0x400d00 <_ZTI7Point3d>, #"typeinfo for Point3d"
      0x400848 <Point2d::Foo()>,
      0x400852 <Point2d::Boo()>,
      0x40085c <Point2d::non_overwrite()>,
      0x6020b0 <_ZTVN10__cxxabiv121__vmi_class_type_infoE@@CXXABI_1.3+16>,
      0x400d28 <_ZTS8Vertex3d>,
      0x200000002,
      0x400cc0 <_ZTI6Vertex>, #"typeinfo for Vertex"
      0x2,
      0x400d00 <_ZTI7Point3d>, #"typeinfo for Point3d"
      0x1002,
      0x0,
      0x6020b0 <_ZTVN10__cxxabiv121__vmi_class_type_infoE@@CXXABI_1.3+16>,
      0x400d32 <_ZTS6Vertex>,
      0x100000000,
      0x400d40 <_ZTI7Point2d>,
      0xffffffffffffe803,
      0x0,
      0x0}
    

    有兴趣的话可以看一下反汇编的vtable的构成。

    参考:

    1. http://stackoverflow.com/questions/6191678/print-c-vtables-using-gdb

    2. http://stackoverflow.com/questions/18363899/how-to-display-a-vtable-by-name-using-gdb


    尊重原创,转载请注明出处: anzhsoft http://blog.csdn.net/anzhsoft/article/details/18600163

  • 相关阅读:
    git分支操作
    redis 和 memcached 有什么区别?redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
    缓存如果使用不当会造成什么后果?
    在项目中缓存是如何使用的?
    excel poi3.17导出导入
    Mongodb: Sort operation used more than the maximum 33554432 bytes of RAM
    VMware12上安装CentOS7
    校验文件是否是Excel文件
    读后感——《构建之法》第1.2.3章
    操作系统——实验一
  • 原文地址:https://www.cnblogs.com/anzhsoft/p/3602966.html
Copyright © 2011-2022 走看看