C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器

http://saturnman.blog.163.com/blog/static/557611201081421344244/

对于C++带于虚函数的单继承类的构造过程我原来写过一篇日志,感觉还有一些不够明确,这里通过调式器再看一下到底构造过程中发生了什么。

本文是通过对msvcgcc这两大主流编译器的编译结果调式来探所在带于虚函数的单继承类的构造过程中发生的事情,对于其它编译器可能结果不与此相同,本文仅供参考,鉴于本文与我前一篇日志有些关联,如果能先读一下那篇日志可能对于理解本文有帮助,上篇地址如下:

http://saturnman.blog.163.com/blog/static/557611201062910383394/

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<string>
using namespace std;
//Function to show binary info of an object
typedef unsigned char* pointer_type;
void show_byte(pointer_type pointer,int length);
void show_byte(pointer_type pointer,int length)
{
    cout<<"address of object:"<<hex<<(int)pointer<<endl;
    cout<<"var size in byte:"<<dec<<length<<endl;
    for(int i=0;i<length;++i)
    {
        cout<<"|address: 0x"<<hex<<(int)(pointer+i)<<" | value:0x"<<(int)(*(pointer+i))<<" |"<<endl;
    }
    cout<<endl;
}
class Base
{
public:
    Base()
    {
        cout<<"In Base::constructor."<<endl;
        cout<<"Constructing "<<*m_pObj_Name<<endl;
        cout<<"binary info:"<<endl;
        show_byte((pointer_type)this,sizeof(*this));
    }
    Base(const string& name):
    flag_var(0x12345678),
        m_pObj_Name(new string(name))
    {  
        cout<<"In Base::constructor."<<endl;
        cout<<"Constructing "<<*m_pObj_Name<<endl;
        cout<<"binary info:"<<endl;
        show_byte((pointer_type)this,sizeof(*this));
    }
    void Util()
    {
        virtual_func1();
    }
    virtual ~Base()  {delete m_pObj_Name;}
    const string& GetName()
    {
        return *(this->m_pObj_Name);
    }
private:
    int flag_var;
    string* m_pObj_Name;
    virtual void pure_virtual_1() = 0;//pure virtual function
    virtual void pure_virtual_2() = 0;//pure virtual function
    virtual void virtual_func1()      //normal virtual function
    {
        cout<<"virtual function in Base."<<endl;
    }
};
class Derived: public Base
{
public:
    Derived(string name): //initialize list
      //defined for testing invoke order of initialize list and base constructor
      a("10"),
      Base(name)
      {
          cout<<"In Derived::constructor."<<endl;
          cout<<"binary info:"<<endl;
          show_byte((pointer_type)this,sizeof(*this));
      }
      void Util()
      {
          virtual_func1();
      }
      virtual ~Derived()  {}
private:
    virtual void pure_virtual_1() { printf("pure_virtual_1() in drived\n"); }
    virtual void pure_virtual_2() { printf("pure_virtual_2() in drived\n"); }
    virtual void virtual_func1()
    {
        cout<<"virtual function in Derived."<<endl;
    }
    //debug
    string a;
};
int main()
{
    char buff1[1024];
    char buff2[1024];
    Derived* d1 = new(buff1) Derived("Derived_1");
    Base* b1 = d1;
    cout<<"After construct:"<<d1->GetName()<<endl;
    show_byte((pointer_type)d1,sizeof(d1));
    d1->Util();
    b1->Util();
    Derived* d2 = new(buff2) Derived("Derived_2");
    Base* b2 = d2;
    cout<<"After construct:"<<d2->GetName()<<endl;
    show_byte((pointer_type)d2,sizeof(d2));
    d2->Util();
    b2->Util();
    d1->~Derived();
    d2->~Derived();
    return 0;
}

编译器版本如下:
gcc
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=c:/mingw32/bin/../libexec/gcc/mingw32/4.5.0/lto-wrapper.exe
Target: mingw32
Configured with: ../../gcc-4.5.0/configure --prefix=/mingw --build=mingw32 --ena
ble-languages=c,ada,c++,fortran,objc,obj-c++ --disable-nls --disable-win32-regis
try --enable-libgomp --disable-werror --enable-threads --disable-symvers --enabl
e-cxx-flags='-fno-function-sections -fno-data-sections' --enable-fully-dynamic-s
tring --enable-libstdcxx-debug --enable-version-specific-runtime-libs --with-pkg
version=tdm-1 --enable-sjlj-exceptions --with-bugurl=http://tdm-gcc.tdragon.net/
bugs
Thread model: win32
gcc version 4.5.0 (tdm-1)
 
msvc
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.
 
为了便于理解本文,我们再回顾一下类的基本构造与销毁过程,可分为如下四步:1.allocate 2.construct 3.destruct 4.deallocate 对于这四步并不一定是连续的,可以先申请空间(allocate),在等待一定时机或是某个条件触发后,再进行类的构造,典型的例子stl的容器类,比如vector,对其调用reserve()函数后,其空间增大,但是并没有在空间上调用默认构构函数对容器元素进行构造,只有当push_back()之类的函数调用发生时再构造相应的函数,这样可以极大节约进行时开销。好,类的一般构造过程就回顾到这里,为了调式类的构造过程,我们在此使用了预先申请好的内存空间,之后利用new布局操作符把类布局到我们的空间上,这时类的构造过程就少了allocatedeallocate。如果你对new布局操作符不熟悉,可以找一本基础C++书看一下,一般都会有的,如果没有就换一本(记得把原来那本烧掉J)。这里使用new布局操作符是为了查看我们的内存发生了什么,来了解类构造的过程。
    下图是在vs2010上断下的情形,先找到buff1的地址,之后把这个地址写到内存查看中,这样做是因为当buff1出了作用域时仍然可以监视内存的变化。

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

则开始,buff1的内存地址放入监视。

先跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 由于我们的内存是事先分配好的,类只要布局上去就可以了,这个内联函数很简单,直接返回地址,其实在编译后这个函数不存在代码,只是一个地址,不要对此感到奇怪。

之后跳出来到下图

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

此时回到主函数,再跟进。

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 这是把参数字串构造成

string对象的构造函数

跳出。

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 又回到主函数

再跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

子类构造函数【这里还没有运行子类构造函数体任何代码】

跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

先调用基类构造函数,【这里还没有运行基类构造函数任何代码】

再跟进。

此时发生了两件事,

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

内存发生变化,vptr被写入,程序到达内存申请函数外

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

跳出。

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

又到达子类构造函数外,此时参数string构造完成

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

String类构造函数

跳出

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

跟进,内存发生变化,m_pObj_Name值发生变化

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

到达基类构造函数体。

我们发现是在vptr值改变完成后再对初始化列表内的值进行初始化的。

跳出

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

到达子类构造函数体外。

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

内存发生变化

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

程序到达string参数构造函数

再跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

再进一步,程序到达子类函数体

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

到此我们得到如下构造顺序

1.构造子类构造函数的参数

2.子类调用基类构造函数

3.基类设置vptr

4.基类初始化列表内容进行构造

5. 基类函数体调用

6. 子类设置vptr

7. 子类初始化列表内容进行构造

8. 子类构造函数体调用

(注意一点,初始化列表内的数据不按书写顺序,而是按类内部的定义顺序)

再看看gcc对于带于虚函数的单继承类的构造过程,我试用了eclipse,code blocks,qt-creater分别进行调式,他们对于内存查看的支持都不正常,有的甚至难以使用。看来M$的工具的易用性还是其它所不能比的,最终我只能在命令行手动了。下面是一份log

GNU gdb (GDB) 7.1

Copyright (C) 2010 Free Software Foundation, Inc.

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software: you are free to change and redistribute it.

There is NO WARRANTY, to the extent permitted by law.  Type "show copying"

and "show warranty" for details.

This GDB was configured as "mingw32".

For bug reporting instructions, please see:

<http://www.gnu.org/software/gdb/bugs/>...

Reading symbols from C:\Users\saturnman\cpp/construct.exe...done.

(gdb) start

Temporary breakpoint 1 at 0x4014cd: file construct.cpp, line 88.

Starting program: C:\Users\saturnman\cpp/construct.exe

[New Thread 6716.0x2500]

 

Temporary breakpoint 1, main () at construct.cpp:88

88              Derived* d1 = new(buff1) Derived("Derived_1");

(gdb) set logging  on

Copying output to gdb.txt.

(gdb) p (void*) buff1

$1 = (void *) 0x28faf0

(gdb) x /20x 0x28faf0

0x28faf0:       0x008a0150      0x008a9418      0x00000001      0x00000011

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

operator new (__p=0x28faf0)

    at c:/mingw32/bin/../lib/gcc/mingw32/4.5.0/include/c++/new:103

103     inline void* operator new(std::size_t, void* __p) throw() { return __p;

}

(gdb)

Derived (this=0x28faf0, name=...) at construct.cpp:63

63                Base(name)

(gdb) x /20x 0x28faf0

0x28faf0:       0x008a0150      0x008a9418      0x00000001      0x00000011

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

Base (this=0x28faf0, name=...) at construct.cpp:31

31                      m_pObj_Name(new string(name))

(gdb) x /20x 0x28faf0

0x28faf0:       0x008a0150      0x008a9418      0x00000001      0x00000011

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

33                      cout<<"In Base::constructor."<<endl;

(gdb) x /20x 0x28faf0

0x28faf0:       0x00473638      0x12345678      0x003445c0      0x00000011

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

In Base::constructor.

34                      cout<<"Constructing "<<*m_pObj_Name<<endl;

(gdb)

Constructing Derived_1

35                      cout<<"binary info:"<<endl;

(gdb)

binary info:

36                      show_byte((pointer_type)this,sizeof(*this));

(gdb)

show_byte (pointer=0x28faf0 "86G", length=12) at construct.cpp:11

11              cout<<"address of object:"<<hex<<(int)pointer<<endl;

(gdb) finish

Run till exit from #0  show_byte (pointer=0x28faf0 "86G", length=12)

    at construct.cpp:11

address of object:28faf0

var size in byte:12

|address: 0x28faf0 | value:0x38 |

|address: 0x28faf1 | value:0x36 |

|address: 0x28faf2 | value:0x47 |

|address: 0x28faf3 | value:0x0 |

|address: 0x28faf4 | value:0x78 |

|address: 0x28faf5 | value:0x56 |

|address: 0x28faf6 | value:0x34 |

|address: 0x28faf7 | value:0x12 |

|address: 0x28faf8 | value:0xc0 |

|address: 0x28faf9 | value:0x45 |

|address: 0x28fafa | value:0x34 |

|address: 0x28fafb | value:0x0 |

 

0x0041fe4e in Base (this=0x28faf0, name=...) at construct.cpp:36

36                      show_byte((pointer_type)this,sizeof(*this));

(gdb) x /20x 0x28faf0

0x28faf0:       0x00473638      0x12345678      0x003445c0      0x00000011

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

_Unwind_SjLj_Unregister (fc=0x28f59c)

    at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:208

208     ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c: No such file or direc

tory.

        in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c

(gdb)

_Unwind_SjLj_SetContext (fc=0x28f59c)

    at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:195

195     in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c

(gdb)

198     in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c

(gdb)

199     in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c

(gdb)

__gthread_setspecific (fc=0x28f59c)

    at ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h:621

621     ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h: No such file or direct

ory.

        in ../../../../gcc-4.5.0/libgcc/../gcc/gthr-win32.h

(gdb)

_Unwind_SjLj_Unregister (fc=0x28f59c)

    at ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c:209

209     ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c: No such file or direc

tory.

        in ../../../../gcc-4.5.0/libgcc/../gcc/unwind-sjlj.c

(gdb)

Base (this=0x28faf0, name=...) at construct.cpp:37

37              }

(gdb)

Derived (this=0x28faf0, name=...) at construct.cpp:65

65                        cout<<"In Derived::constructor."<<endl;

(gdb) x /20x 0x28faf0

0x28faf0:       0x00473658      0x12345678      0x003445c0      0x003445ec

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) step

In Derived::constructor.

66                        cout<<"binary info:"<<endl;

(gdb) x /20x 0x28faf0

0x28faf0:       0x00473658      0x12345678      0x003445c0      0x003445ec

0x28fb00:       0x00000004      0x00000000      0x008a9380      0x00000000

0x28fb10:       0x00344588      0x7790a47f      0x00344598      0x00000a48

0x28fb20:       0xfeeefeee      0x00000005      0x00344560      0x00340000

0x28fb30:       0x00344fe0      0x7794fd4e      0x7b04057a      0x00000011

(gdb) q

A debugging session is active.

 

        Inferior 1 [process 6716] will be killed.

 

Quit anyway? (y or n) y

能看懂的就看一下,我把关键部分加红了,其与vs2010的差别不大,但是其设置vptr却是在初使化列表完成之后,构造函数体内代码运行之前,可以认为gcc是把设置vptr的代码入在了构造函数体代码的最前端。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值