C++单重继承分析
阅读原文时间:2023年07月16日阅读:2

code[class*="language-"], pre[class*="language-"] { color: rgba(51, 51, 51, 1); font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.4; -moz-tab-size: 8; -o-tab-size: 8; tab-size: 8; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none }
pre[class*="language-"] { padding: 0.8em; overflow: auto; border-radius: 3px; background: rgba(245, 245, 245, 1) }
:not(pre)>code[class*="language-"] { padding: 0.1em; border-radius: 0.3em; white-space: normal; background: rgba(245, 245, 245, 1) }
.token.comment, .token.blockquote { color: rgba(150, 152, 150, 1) }
.token.cdata { color: rgba(24, 54, 145, 1) }
.token.doctype, .token.punctuation, .token.variable, .token.macro.property { color: rgba(51, 51, 51, 1) }
.token.operator, .token.important, .token.keyword, .token.rule, .token.builtin { color: rgba(167, 29, 93, 1) }
.token.string, .token.url, .token.regex, .token.attr-value { color: rgba(24, 54, 145, 1) }
.token.property, .token.number, .token.boolean, .token.entity, .token.atrule, .token.constant, .token.symbol, .token.command, .token.code { color: rgba(0, 134, 179, 1) }
.token.tag, .token.selector, .token.prolog { color: rgba(99, 163, 92, 1) }
.token.function, .token.namespace, .token.pseudo-element, .token.class, .token.class-name, .token.pseudo-class, .token.id, .token.url-reference .token.variable, .token.attr-name { color: rgba(121, 93, 163, 1) }
.token.entity { cursor: help }
.token.title, .token.title .token.punctuation { font-weight: bold; color: rgba(29, 62, 129, 1) }
.token.list { color: rgba(237, 106, 67, 1) }
.token.inserted { background-color: rgba(234, 255, 234, 1); color: rgba(85, 165, 50, 1) }
.token.deleted { background-color: rgba(255, 236, 236, 1); color: rgba(189, 44, 0, 1) }
.token.bold { font-weight: bold }
.token.italic { font-style: italic }
.language-json .token.property { color: rgba(24, 54, 145, 1) }
.language-markup .token.tag .token.punctuation { color: rgba(51, 51, 51, 1) }
code.language-css, .language-css .token.function { color: rgba(0, 134, 179, 1) }
.language-yaml .token.atrule { color: rgba(99, 163, 92, 1) }
code.language-yaml { color: rgba(24, 54, 145, 1) }
.language-ruby .token.function { color: rgba(51, 51, 51, 1) }
.language-markdown .token.url { color: rgba(121, 93, 163, 1) }
.language-makefile .token.symbol { color: rgba(121, 93, 163, 1) }
.language-makefile .token.variable { color: rgba(24, 54, 145, 1) }
.language-makefile .token.builtin { color: rgba(0, 134, 179, 1) }
.language-bash .token.keyword { color: rgba(0, 134, 179, 1) }
pre[data-line] { position: relative; padding: 1em 0 1em 3em }
pre[data-line] .line-highlight-wrapper { position: absolute; top: 0; left: 0; background-color: rgba(0, 0, 0, 0); display: block; width: 100% }
pre[data-line] .line-highlight { position: absolute; left: 0; right: 0; margin-top: 1em; background: linear-gradient(90deg, rgba(153, 122, 102, 0.1) 70%, rgba(153, 122, 102, 0)); pointer-events: none; line-height: inherit; white-space: pre }
pre[data-line] .line-highlight:before, pre[data-line] .line-highlight[data-end]:after { content: attr(data-start); position: absolute; top: 0.4em; left: 0.6em; min-width: 1em; padding: 0 0.5em; background-color: rgba(153, 122, 102, 0.4); color: rgba(245, 242, 240, 1); font: bold 65% / 1.5 sans-serif; text-align: center; vertical-align: 0.3em; border-radius: 999px; text-shadow: none; box-shadow: 0 1px rgba(255, 255, 255, 1) }
pre[data-line] .line-highlight[data-end]:after { content: attr(data-end); top: auto; bottom: 0.4em }
html body { font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif; font-size: 16px; line-height: 1.6; color: rgba(51, 51, 51, 1); background-color: rgba(255, 255, 255, 1); overflow: initial; box-sizing: border-box; word-wrap: break-word }
html body>:first-child { margin-top: 0 }
html body h1, html body h2, html body h3, html body h4, html body h5, html body h6 { line-height: 1.2; margin-top: 1em; margin-bottom: 16px; color: rgba(0, 0, 0, 1) }
html body h1 { font-size: 2.25em; font-weight: 300; padding-bottom: 0.3em }
html body h2 { font-size: 1.75em; font-weight: 400; padding-bottom: 0.3em }
html body h3 { font-size: 1.5em; font-weight: 500 }
html body h4 { font-size: 1.25em; font-weight: 600 }
html body h5 { font-size: 1.1em; font-weight: 600 }
html body h6 { font-size: 1em; font-weight: 600 }
html body h1, html body h2, html body h3, html body h4, html body h5 { font-weight: 600 }
html body h5 { font-size: 1em }
html body h6 { color: rgba(92, 92, 92, 1) }
html body strong { color: rgba(0, 0, 0, 1) }
html body del { color: rgba(92, 92, 92, 1) }
html body a:not([href]) { color: inherit; text-decoration: none }
html body a { color: rgba(0, 136, 204, 1); text-decoration: none }
html body a:hover { color: rgba(0, 163, 245, 1); text-decoration: none }
html body img { max-width: 100% }
html body>p { margin-top: 0; margin-bottom: 16px; word-wrap: break-word }
html body>ul, html body>ol { margin-bottom: 16px }
html body ul, html body ol { padding-left: 2em }
html body ul.no-list, html body ol.no-list { padding: 0; list-style-type: none }
html body ul ul, html body ul ol, html body ol ol, html body ol ul { margin-top: 0; margin-bottom: 0 }
html body li { margin-bottom: 0 }
html body li.task-list-item { list-style: none }
html body li>p { margin-top: 0; margin-bottom: 0 }
html body .task-list-item-checkbox { margin: 0 0.2em 0.25em -1.8em; vertical-align: middle }
html body .task-list-item-checkbox:hover { cursor: pointer }
html body blockquote { margin: 16px 0; font-size: inherit; padding: 0 15px; color: rgba(92, 92, 92, 1); border-left: 4px solid rgba(214, 214, 214, 1) }
html body blockquote>:first-child { margin-top: 0 }
html body blockquote>:last-child { margin-bottom: 0 }
html body hr { height: 4px; margin: 32px 0; background-color: rgba(214, 214, 214, 1); border: 0 none }
html body table { margin: 10px 0 15px; border-collapse: collapse; border-spacing: 0; display: block; width: 100%; overflow: auto; word-break: keep-all }
html body table th { font-weight: bold; color: rgba(0, 0, 0, 1) }
html body table td, html body table th { border: 1px solid rgba(214, 214, 214, 1); padding: 6px 13px }
html body dl { padding: 0 }
html body dl dt { padding: 0; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: bold }
html body dl dd { padding: 0 16px; margin-bottom: 16px }
html body code { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.85em !important; color: rgba(0, 0, 0, 1); background-color: rgba(240, 240, 240, 1); border-radius: 3px; padding: 0.2em 0 }
html body code::before, html body code::after { letter-spacing: -0.2em; content: " " }
html body pre>code { padding: 0; margin: 0; font-size: 0.85em !important; word-break: normal; white-space: pre; background: rgba(0, 0, 0, 0); border: 0 }
html body .highlight { margin-bottom: 16px }
html body .highlight pre, html body pre { padding: 1em; overflow: auto; font-size: 0.85em !important; line-height: 1.45; border: rgba(214, 214, 214, 1); border-radius: 3px }
html body .highlight pre { margin-bottom: 0; word-break: normal }
html body pre code, html body pre tt { display: inline; max-width: initial; padding: 0; margin: 0; overflow: initial; line-height: inherit; word-wrap: normal; background-color: rgba(0, 0, 0, 0); border: 0 }
html body pre code:before, html body pre tt:before, html body pre code:after, html body pre tt:after { content: normal }
html body p, html body blockquote, html body ul, html body ol, html body dl, html body pre { margin-top: 0; margin-bottom: 16px }
html body kbd { color: rgba(0, 0, 0, 1); border-top: 1px solid rgba(214, 214, 214, 1); border-right: 1px solid rgba(214, 214, 214, 1); border-bottom: 2px solid rgba(199, 199, 199, 1); border-left: 1px solid rgba(214, 214, 214, 1); padding: 2px 4px; background-color: rgba(240, 240, 240, 1); border-radius: 3px }
@media print { html body { background-color: rgba(255, 255, 255, 1) } html body h1, html body h2, html body h3, html body h4, html body h5, html body h6 { color: rgba(0, 0, 0, 1); page-break-after: avoid } html body blockquote { color: rgba(92, 92, 92, 1) } html body pre { page-break-inside: avoid } html body table { display: table } html body img { display: block; max-width: 100%; max-height: 100% } html body pre, html body code { word-wrap: break-word; white-space: pre } }
.markdown-preview { width: 100%; height: 100%; box-sizing: border-box }
.markdown-preview .pagebreak, .markdown-preview .newpage { page-break-before: always }
.markdown-preview pre.line-numbers { position: relative; padding-left: 3.8em; counter-reset: linenumber 0 }
.markdown-preview pre.line-numbers>code { position: relative }
.markdown-preview pre.line-numbers .line-numbers-rows { position: absolute; pointer-events: none; top: 1em; font-size: 100%; left: 0; width: 3em; letter-spacing: -1px; border-right: 1px solid rgba(153, 153, 153, 1); -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none }
.markdown-preview pre.line-numbers .line-numbers-rows>span { pointer-events: none; display: block; counter-increment: linenumber 1 }
.markdown-preview pre.line-numbers .line-numbers-rows>span:before { content: counter(linenumber); color: rgba(153, 153, 153, 1); display: block; padding-right: 0.8em; text-align: right }
.markdown-preview .mathjax-exps .MathJax_Display { text-align: center !important }
.markdown-preview:not([for="preview"]) .code-chunk .btn-group { display: none }
.markdown-preview:not([for="preview"]) .code-chunk .status { display: none }
.markdown-preview:not([for="preview"]) .code-chunk .output-div { margin-bottom: 16px }
{ width: 8px }
{ border-radius: 10px; background-color: rgba(0, 0, 0, 0) }
{ border-radius: 5px; background-color: rgba(150, 150, 150, 0.66); border: 4px solid rgba(150, 150, 150, 0.66); background-clip: content-box }
html body[for="html-export"]:not([data-presentation-mode]) { position: relative; width: 100%; height: 100%; top: 0; left: 0; margin: 0; padding: 0; overflow: auto }
html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview { position: relative; top: 0 }
@media screen and (min-width: 914px) { html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview { padding: 2em calc(50% - 457px + 2em) } }
@media screen and (max-width: 914px) { html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview { padding: 2em } }
@media screen and (max-width: 450px) { html body[for="html-export"]:not([data-presentation-mode]) .markdown-preview { font-size: 14px !important; padding: 1em } }
@media print { html body[for="html-export"]:not([data-presentation-mode]) #sidebar-toc-btn { display: none } }
html body[for="html-export"]:not([data-presentation-mode]) #sidebar-toc-btn { position: fixed; bottom: 8px; left: 8px; font-size: 28px; cursor: pointer; color: inherit; z-index: 99; width: 32px; text-align: center; opacity: 0.4 }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] #sidebar-toc-btn { opacity: 1 }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc { position: fixed; top: 0; left: 0; width: 300px; height: 100%; padding: 32px 0 48px; font-size: 14px; box-shadow: 0 0 4px rgba(150, 150, 150, 0.33); box-sizing: border-box; overflow: auto; background-color: inherit }
{ width: 8px }
{ border-radius: 10px; background-color: rgba(0, 0, 0, 0) }
{ border-radius: 5px; background-color: rgba(150, 150, 150, 0.66); border: 4px solid rgba(150, 150, 150, 0.66); background-clip: content-box }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc a { text-decoration: none }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc ul { padding: 0 1.6em; margin-top: 0.8em }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc li { margin-bottom: 0.8em }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .md-sidebar-toc ul { list-style-type: none }
html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview { left: 300px; width: calc(100% - 300px); padding: 2em calc(50% - 457px - 150px); margin: 0; box-sizing: border-box }
@media screen and (max-width: 1274px) { html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview { padding: 2em } }
@media screen and (max-width: 450px) { html body[for="html-export"]:not([data-presentation-mode])[html-show-sidebar-toc] .markdown-preview { width: 100% } }
html body[for="html-export"]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .markdown-preview { left: 50%; transform: translateX(-50%) }
html body[for="html-export"]:not([data-presentation-mode]):not([html-show-sidebar-toc]) .md-sidebar-toc { display: none }

测试代码如下:

class CBase
{
int m_nTest;
public:
CBase():m_nTest(0)
{
std::cout << "CBase()" << std::endl;
}

~CBase()
{
std::cout << "~CBase()" << std::endl;
}

void ShowInfo1() { std::cout << m_nTest + 1 << std::endl; }
void ShowInfo() { std::cout << m_nTest << std::endl; }
void ShowInfo2() { std::cout << m_nTest + 2 << std::endl; }
};

class CDerived:public CBase
{
int m_nTest1;
public:
CDerived() :m_nTest1(1)
{
std::cout << "CDerived()" << std::endl;
}

~CDerived()
{
std::cout << "~CDerived()" << std::endl;
}
};

int main(int argc, char* argv[])
{
CDerived t;
return 0;
}

类对象的内存结构如下:

  • 基类中有虚函数,派生类中无新增虚函数,派生类中没有重写父类的虚函数

    测试代码如下:

    class CBase
    {
    int m_nTest;
    public:
    CBase();
    ~CBase();

    virtual void ShowInfo1();
    virtual void ShowInfo();
    virtual void ShowInfo2();
    };

    CBase::CBase() :m_nTest(0)
    {
    std::cout << "CBase()" << std::endl;
    }

    CBase::~CBase()
    {
    std::cout << "~CBase()" << std::endl;
    }

    void CBase::ShowInfo1()
    {
    std::cout << m_nTest + 1 << std::endl;
    }

    void CBase::ShowInfo()
    {
    std::cout << m_nTest << std::endl;
    }

    void CBase::ShowInfo2()
    {
    std::cout << m_nTest + 2 << std::endl;
    }

    class CDerived:public CBase
    {
    int m_nTest1;
    public:
    CDerived();
    ~CDerived();
    };

    CDerived::CDerived() :m_nTest1(1)
    {
    std::cout << "CDerived()" << std::endl;
    }

    CDerived::~CDerived()
    {
    std::cout << "~CDerived()" << std::endl;
    }

    int main(int argc, char* argv[])
    {
    CDerived t;
    return 0;
    }

    CDerived继承自CBase,所以在CDerived类对象构造的时候先调用CBase的构造函数,此时CBase的构造函数

    会CBase类的虚表指针复制到CDerived对象存放虚表指针的位置(对象开始的四个字节),过程如下:

    可以看出CBase类的数据成员m_nTest还未初始化,所以父类覆盖虚表指针的时间是在类数据成员初始化之前

    CBase类构造函数执行完成后,CDerived的构造函数开始执行:

    通过对比可以发现CDerived类的构造函数执行时再次覆盖了虚表指针,当前的虚表指针指向了CDerived类

    自己的虚表;虽然CDerived类继承了CBase类,但是CDerived类并没有重写父类的虚函数,所以VC++编译器

    将CBase的虚表复制给了CDerived类的虚表

  • 基类中有虚函数,派生类中无新增虚函数,派生类中重写了父类的虚函数

    在Derived类中重写两个父类的虚函数:

    class CDerived:public CBase
    {
    int m_nTest1;
    public:
    CDerived();
    ~CDerived();
    virtual void ShowInfo1();
    virtual void ShowInfo();
    };

    void CDerived::ShowInfo1()
    {
    std::cout << " CDerived::ShowIfno1:" << m_nTest1 << std::endl;
    }

    void CDerived::ShowInfo()
    {
    std::cout << " CDerived::ShowInfo:" << m_nTest1 << std::endl;
    }

    CDerived::CDerived() :m_nTest1(1)
    {
    std::cout << "CDerived()" << std::endl;
    }

    CDerived::~CDerived()
    {
    std::cout << "~CDerived()" << std::endl;
    }

    观察CDerived对象构造过程中,虚表变化:

    CBase类的构造函数执行时虚表和虚表指针的变化:

    在CBase类的构造函数执行过程中,对象的虚表指针始终指向CBase类的虚表

    CDerived类的构造函数执行时虚表和虚表指针的变化:

    因为CDerived类重写了前两个虚函数,所以CDerived类的虚表前两个函数指针使用的CDerived自己的虚函

    数地址来填充,第三个虚函数因为没有重写,所以依旧使用CBase实现的虚函数的地址填充

     

    接下来观察下CDervied对象析构的过程:

    可以看出在CDervied的析构函数执行的过程中虚表和虚表指针没有发生变化,看看其反汇编代码:

    CDerived::~CDerived()
    {
    000A2360 55 push ebp
    000A2361 8B EC mov ebp,esp
    000A2363 6A FF push 0FFFFFFFFh
    000A2365 68 B0 6F 0A 00 push 0A6FB0h
    000A236A 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]
    000A2370 50 push eax
    000A2371 81 EC CC 00 00 00 sub esp,0CCh
    000A2377 53 push ebx
    000A2378 56 push esi
    000A2379 57 push edi
    000A237A 51 push ecx
    000A237B 8D BD 28 FF FF FF lea edi,[ebp-0D8h]
    000A2381 B9 33 00 00 00 mov ecx,33h
    000A2386 B8 CC CC CC CC mov eax,0CCCCCCCCh
    000A238B F3 AB rep stos dword ptr es:[edi]
    000A238D 59 pop ecx
    000A238E A1 04 C0 0A 00 mov eax,dword ptr [__security_cookie (0AC004h)]
    000A2393 33 C5 xor eax,ebp
    000A2395 50 push eax
    000A2396 8D 45 F4 lea eax,[ebp-0Ch]
    000A2399 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax
    000A239F 89 4D EC mov dword ptr [this],ecx
    000A23A2 8B 45 EC mov eax,dword ptr [this]
    000A23A5 C7 00 60 9B 0A 00 mov dword ptr [eax],offset CDerived::`vftable' (0A9B60h)
    std::cout << "~CDerived()" << std::endl; 000A23AB 8B F4 mov esi,esp 000A23AD 68 9B 10 0A 00 push offset std::endl > (0A109Bh)
    000A23B2 68 38 9D 0A 00 push offset string "~CDerived()" (0A9D38h)
    000A23B7 A1 98 D0 0A 00 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0AD098h)]
    000A23BC 50 push eax
    000A23BD E8 E5 EF FF FF call std::operator<< > (0A13A7h)
    000A23C2 83 C4 08 add esp,8
    000A23C5 8B C8 mov ecx,eax
    000A23C7 FF 15 A8 D0 0A 00 call dword ptr [__imp_std::basic_ostream >::operator<< (0AD0A8h)]
    000A23CD 3B F4 cmp esi,esp
    000A23CF E8 9E ED FF FF call __RTC_CheckEsp (0A1172h)
    }
    000A23D4 8B 4D EC mov ecx,dword ptr [this]
    000A23D7 E8 3F EF FF FF call CBase::~CBase (0A131Bh)
    000A23DC 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
    000A23DF 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx
    000A23E6 59 pop ecx
    000A23E7 5F pop edi
    000A23E8 5E pop esi
    000A23E9 5B pop ebx
    000A23EA 81 C4 D8 00 00 00 add esp,0D8h
    000A23F0 3B EC cmp ebp,esp
    000A23F2 E8 7B ED FF FF call __RTC_CheckEsp (0A1172h)
    000A23F7 8B E5 mov esp,ebp
    000A23F9 5D pop ebp
    000A23FA C3 ret

    但是从这句汇编代码:

    000A23A5 C7 00 60 9B 0A 00 mov dword ptr [eax],offset CDerived::`vftable' (0A9B60h)

    可以看出CDerived类的析构函数在执行时还是做了一次虚表指针覆盖动作,用CDerived的虚表地址赋值给

    对象的虚表指针,但在CDerived类的构造函数执行前对象的虚表指针本来就指向CDerived的虚表,所以感觉

    VC++编译器生成的这句代码是多余的,有可能是因为当前程序版本是dubug版本,为了方便调试就多生成了一

    些代码。

    CDerived类的析构函数执行完成后,在CBase类的析构函数开始执行:

    此时对象的虚表指针被CBase的析构函数置为指向CBase类虚表

  • 基类中有虚函数,派生类中有新增虚函数,并且派生类中重写父类的虚函数

    CDerived代码如下:

    class CDerived:public CBase
    {
    int m_nTest1;
    public:
    CDerived();
    ~CDerived();
    virtual void NewVirtualFuction1();
    virtual void ShowInfo1();
    virtual void ShowInfo();
    virtual void NewVirtualFuction2();
    };

    void CDerived::NewVirtualFuction1()
    {
    std::cout << " CDerived::NewVirtualFuction:" << m_nTest1 << std::endl;
    }

    void CDerived::NewVirtualFuction2()
    {
    std::cout << " CDerived::NewVirtualFuction:" << m_nTest1 << std::endl;
    }

    void CDerived::ShowInfo1()
    {
    std::cout << " CDerived::ShowIfno1:" << m_nTest1 << std::endl;
    }

    void CDerived::ShowInfo()
    {
    std::cout << " CDerived::ShowInfo:" << m_nTest1 << std::endl;
    }

    CDerived::CDerived() :m_nTest1(1)
    {
    std::cout << "CDerived()" << std::endl;
    }

    CDerived:: ~CDerived()
    {
    std::cout << "~CDerived()" << std::endl;
    }

    先是CBase类的构造函数进行虚表指针覆盖:

    然后CDerived类的构造函数进行虚表指针的覆盖:

    可以VC++编译器在为CDerived类生成虚表时,先将父类的虚表复制一份,如果子类重写了父类的某个虚函数,

    就用子类重写的虚函数的地址覆盖虚表中的对应位置,子类自己新增的虚函数则全部追加到这个副本的尾部,

    最终形成了子类自己的虚表

代码如下:

class CBase
{
int m_nTest;
public:
CBase();
virtual ~CBase();
};

CBase::CBase() :m_nTest(0)
{
std::cout << "CBase()" << std::endl;
}

CBase::~CBase()
{
std::cout << "~CBase()" << std::endl;
}

class CDerived:public CBase
{
int m_nTest1;
public:
CDerived();
~CDerived();
};

CDerived::CDerived() :m_nTest1(1)
{
std::cout << "CDerived()" << std::endl;
}

CDerived::~CDerived()
{
std::cout << "~CDerived()" << std::endl;
}

int main(int argc, char* argv[])
{
CBase * p = new CDerived;
delete p;
return 0;
}

运行结果:

再来看看其反汇编代码:

delete p;
000D6649 89 85 FC FE FF FF mov dword ptr [ebp-104h],eax
000D664F 8B 8D FC FE FF FF mov ecx,dword ptr [ebp-104h]
000D6655 89 8D 08 FF FF FF mov dword ptr [ebp-0F8h],ecx
000D665B 83 BD 08 FF FF FF 00 cmp dword ptr [ebp-0F8h],0
000D6662 74 15 je main+0C9h (0D6679h)
000D6664 6A 01 push 1
000D6666 8B 8D 08 FF FF FF mov ecx,dword ptr [ebp-0F8h]

000D666C E8 3A AE FF FF call CBase::`scalar deleting destructor' (0D14ABh)

000D6671 89 85 F4 FE FF FF mov dword ptr [ebp-10Ch],eax
000D6677 EB 0A jmp main+0D3h (0D6683h)
000D6679 C7 85 F4 FE FF FF 00 00 00 00 mov dword ptr [ebp-10Ch],0

可以看出当用一个基类指针指向动态分配的子类类对象,然后直接通过这个基类指针释放对象,VC++编译器生成

的代码只调用了基类的析构函数,函数,子类的析构函数并没有被调用,如果子类对象中引用了其它资源,就造成

了资源泄漏;所以如果一个类作为基类,那么其虚函数必须得是虚函数,否则当使用一个基类指针指向一个动态分

配的子类对象,然后通过这个基类指针去释放动态分配的子类对象时,子类的析构函数会被调用.

现在将基类的析构函数声明前加virtual关键字,使其成为虚析构函数,观察构造过程,以及通过基类指针释放

动态分配的子类对象时的过程:

从这里可以看出析构函数是一个特殊的成员函数,如果一个类的析构函数为虚函数,那么继承它的子类的析构函

数也是虚函数.

在看看此时的反汇编代码:

delete p;
012429B6 8B 45 EC mov eax,dword ptr [p] //取出虚表指针
012429B9 89 85 FC FE FF FF mov dword ptr [ebp-104h],eax
012429BF 8B 8D FC FE FF FF mov ecx,dword ptr [ebp-104h]
012429C5 89 8D 08 FF FF FF mov dword ptr [ebp-0F8h],ecx
012429CB 83 BD 08 FF FF FF 00 cmp dword ptr [ebp-0F8h],0
012429D2 74 25 je main+0D9h (012429F9h)
012429D4 8B F4 mov esi,esp
012429D6 6A 01 push 1
012429D8 8B 95 08 FF FF FF mov edx,dword ptr [ebp-0F8h]
012429DE 8B 02 mov eax,dword ptr [edx]
012429E0 8B 8D 08 FF FF FF mov ecx,dword ptr [ebp-0F8h]
012429E6 8B 10 mov edx,dword ptr [eax]
012429E8 FF D2 call edx //调用CDerived类的析构
012429EA 3B F4 cmp esi,esp
012429EC E8 A4 E7 FF FF call __RTC_CheckEsp (01241195h)
012429F1 89 85 F4 FE FF FF mov dword ptr [ebp-10Ch],eax
012429F7 EB 0A jmp main+0E3h (01242A03h)
012429F9 C7 85 F4 FE FF FF 00 00 00 00 mov dword ptr [ebp-10Ch],0

上面代码中只有析构函数是虚函数,所以析构函数的指针位于虚表中的第一项,所以上面汇编代码中的:

012429E6 8B 10 mov edx,dword ptr [eax]
012429E8 FF D2 call edx

通过虚表调用到CDerived类的析构函数,因为CDerived类继承自CBase类,按照C++标准,子类析构完成后在调用

父类的析构函数,所以VC++编译器直接调用CBase的析构函数的代码插入到CDerived类析构函数的后面,再看下

CDerived类的析构函数的反汇编代码:

CDerived::~CDerived()
{
00F62450 55 push ebp
00F62451 8B EC mov ebp,esp
00F62453 6A FF push 0FFFFFFFFh
00F62455 68 F0 73 F6 00 push 0F673F0h
00F6245A 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h]
00F62460 50 push eax
00F62461 81 EC CC 00 00 00 sub esp,0CCh
00F62467 53 push ebx
00F62468 56 push esi
00F62469 57 push edi
00F6246A 51 push ecx
00F6246B 8D BD 28 FF FF FF lea edi,[ebp-0D8h]
00F62471 B9 33 00 00 00 mov ecx,33h
00F62476 B8 CC CC CC CC mov eax,0CCCCCCCCh
00F6247B F3 AB rep stos dword ptr es:[edi]
00F6247D 59 pop ecx
00F6247E A1 04 C0 F6 00 mov eax,dword ptr [__security_cookie (0F6C004h)]
00F62483 33 C5 xor eax,ebp
00F62485 50 push eax
00F62486 8D 45 F4 lea eax,[ebp-0Ch]
00F62489 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax
00F6248F 89 4D EC mov dword ptr [this],ecx
00F62492 8B 45 EC mov eax,dword ptr [this]
00F62495 C7 00 54 9B F6 00 mov dword ptr [eax],offset CDerived::`vftable' (0F69B54h)
std::cout << "~CDerived()" << std::endl; 00F6249B 8B F4 mov esi,esp 00F6249D 68 96 10 F6 00 push offset std::endl > (0F61096h)
00F624A2 68 64 9B F6 00 push offset string "~CDerived()" (0F69B64h)
00F624A7 A1 98 D0 F6 00 mov eax,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0F6D098h)]
00F624AC 50 push eax
00F624AD E8 59 EF FF FF call std::operator<< > (0F6140Bh)
00F624B2 83 C4 08 add esp,8
00F624B5 8B C8 mov ecx,eax
00F624B7 FF 15 A4 D0 F6 00 call dword ptr [__imp_std::basic_ostream >::operator<< (0F6D0A4h)]
00F624BD 3B F4 cmp esi,esp
00F624BF E8 D1 EC FF FF call __RTC_CheckEsp (0F61195h)
}

/**********VC++编译器将调用CBase的代码偷偷插入到CDerived析构函数的尾部***************/
00F624C4 8B 4D EC mov ecx,dword ptr [this]
00F624C7 E8 02 F0 FF FF call CBase::~CBase (0F614CEh)
/********************************************************************************/
00F624CC 8B 4D F4 mov ecx,dword ptr [ebp-0Ch]
00F624CF 64 89 0D 00 00 00 00 mov dword ptr fs:[0],ecx
00F624D6 59 pop ecx
00F624D7 5F pop edi
00F624D8 5E pop esi
00F624D9 5B pop ebx
00F624DA 81 C4 D8 00 00 00 add esp,0D8h
00F624E0 3B EC cmp ebp,esp
00F624E2 E8 AE EC FF FF call __RTC_CheckEsp (0F61195h)
00F624E7 8B E5 mov esp,ebp
00F624E9 5D pop ebp
00F624EA C3 ret

  • 如果一个类有虚函数,那么这个VC++编译器一定会为其生成虚表,所有对象共用这一个虚表

  • 调用父类构造函数和析构函数期间对象的虚表指针指向父类的虚表,调用子类构造函数后虚表指针指向

    子类自己的虚表,构造和析构期间都会做虚表指针的覆盖动作

  • 一个类如果要作为基类,那么其析构函数一定得是虚函数,以免使用基类指针或者应用释放指向子类对象时

    造成资源泄漏

  • 析构函数是一个特殊的成员函数,如果父类的析构函数是虚函数,那么子类的析构函数也是虚函数

  • 无论是VC++编译器还是其它的C++编译器,为了实现C++标准定义的行为,都会偷偷生成代码

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器