最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Inconsistent destructors as seen by gdb and objdump - Stack Overflow

programmeradmin7浏览0评论

My code is:

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

class Base
{
public:
    Base(uint32_t len)
        : len_(len)
    {
        buf_ = (char *)malloc(len_ * sizeof(char));
    }
    virtual ~Base()
    {
        if (nullptr != buf_)
        {
            free(buf_);
            buf_ = nullptr;
        }
    }
    void set_buf(const char *str)
    {
        if (nullptr != str && nullptr != buf_ && len_ > 0)
        {
            strncpy(buf_, str, len_);
            buf_[len_ - 1] = '\0';
        }
    }

private:
    uint32_t len_;
    char *buf_;
};

int main(int argc, char *argv[])
{
    Base base(8);
    base.set_buf("hello");
    return 0;
}

I compiled it using g++ -g -O0 -fno-inline -std=c++11 -Wall main.cpp -o main.
Next, I used the objdump -S -t main to look at the assembly code:

0000000000001250 <_ZN4BaseD0Ev>:
    virtual ~Base()
    1250:       f3 0f 1e fa             endbr64
    1254:       55                      push   %rbp
    1255:       48 89 fd                mov    %rdi,%rbp
    }
    1258:       e8 d3 ff ff ff          call   1230 <_ZN4BaseD1Ev>

As shown above, you can see that _ZN4BaseD0Ev (i.e., deleting destructor) calls _ZN4BaseD1Ev (i.e., complete object destructor).
However, when I debug main with gdb, I see that _ZN4BaseD0Ev calls _ZN4BaseD2Ev (i.e., base object destructor) and, indeed, it's _ZN4BaseD2Ev in the vtable and not _ZN4BaseD1Ev.

(gdb) disassemble  _ZN4BaseD0Ev
Dump of assembler code for function _ZN4BaseD0Ev:
   0x0000555555555250 <+0>:     endbr64 
   0x0000555555555254 <+4>:     push   %rbp
   0x0000555555555255 <+5>:     mov    %rdi,%rbp
   0x0000555555555258 <+8>:     call   0x555555555230 <_ZN4BaseD2Ev>
   0x000055555555525d <+13>:    mov    %rbp,%rdi
   0x0000555555555260 <+16>:    mov    $0x18,%esi
   0x0000555555555265 <+21>:    pop    %rbp
   0x0000555555555266 <+22>:    jmp    0x5555555550a0 <_ZdlPvm@plt>
End of assembler dump.
(gdb) x/4ag 0x555555557d78 - 16
0x555555557d68 <_ZTV4Base>:     0x0     0x555555557d88 <_ZTI4Base>
0x555555557d78 <_ZTV4Base+16>:  0x555555555230 <_ZN4BaseD2Ev>   0x555555555250 <_ZN4BaseD0Ev>

I'd like to know what causes this discrepancy and what the real situation is (does _ZN4BaseD0Ev actually call _ZN4BaseD1Ev or _ZN4BaseD2Ev)?
My environment info:

$ uname -a
Linux xxx 5.15.0-127-generic #137-Ubuntu SMP Fri Nov 8 15:21:01 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ gdb --version
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1

My code is:

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

class Base
{
public:
    Base(uint32_t len)
        : len_(len)
    {
        buf_ = (char *)malloc(len_ * sizeof(char));
    }
    virtual ~Base()
    {
        if (nullptr != buf_)
        {
            free(buf_);
            buf_ = nullptr;
        }
    }
    void set_buf(const char *str)
    {
        if (nullptr != str && nullptr != buf_ && len_ > 0)
        {
            strncpy(buf_, str, len_);
            buf_[len_ - 1] = '\0';
        }
    }

private:
    uint32_t len_;
    char *buf_;
};

int main(int argc, char *argv[])
{
    Base base(8);
    base.set_buf("hello");
    return 0;
}

I compiled it using g++ -g -O0 -fno-inline -std=c++11 -Wall main.cpp -o main.
Next, I used the objdump -S -t main to look at the assembly code:

0000000000001250 <_ZN4BaseD0Ev>:
    virtual ~Base()
    1250:       f3 0f 1e fa             endbr64
    1254:       55                      push   %rbp
    1255:       48 89 fd                mov    %rdi,%rbp
    }
    1258:       e8 d3 ff ff ff          call   1230 <_ZN4BaseD1Ev>

As shown above, you can see that _ZN4BaseD0Ev (i.e., deleting destructor) calls _ZN4BaseD1Ev (i.e., complete object destructor).
However, when I debug main with gdb, I see that _ZN4BaseD0Ev calls _ZN4BaseD2Ev (i.e., base object destructor) and, indeed, it's _ZN4BaseD2Ev in the vtable and not _ZN4BaseD1Ev.

(gdb) disassemble  _ZN4BaseD0Ev
Dump of assembler code for function _ZN4BaseD0Ev:
   0x0000555555555250 <+0>:     endbr64 
   0x0000555555555254 <+4>:     push   %rbp
   0x0000555555555255 <+5>:     mov    %rdi,%rbp
   0x0000555555555258 <+8>:     call   0x555555555230 <_ZN4BaseD2Ev>
   0x000055555555525d <+13>:    mov    %rbp,%rdi
   0x0000555555555260 <+16>:    mov    $0x18,%esi
   0x0000555555555265 <+21>:    pop    %rbp
   0x0000555555555266 <+22>:    jmp    0x5555555550a0 <_ZdlPvm@plt>
End of assembler dump.
(gdb) x/4ag 0x555555557d78 - 16
0x555555557d68 <_ZTV4Base>:     0x0     0x555555557d88 <_ZTI4Base>
0x555555557d78 <_ZTV4Base+16>:  0x555555555230 <_ZN4BaseD2Ev>   0x555555555250 <_ZN4BaseD0Ev>

I'd like to know what causes this discrepancy and what the real situation is (does _ZN4BaseD0Ev actually call _ZN4BaseD1Ev or _ZN4BaseD2Ev)?
My environment info:

$ uname -a
Linux xxx 5.15.0-127-generic #137-Ubuntu SMP Fri Nov 8 15:21:01 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ gdb --version
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Share Improve this question edited Mar 15 at 20:02 xxllxx666 asked Mar 15 at 19:41 xxllxx666xxllxx666 3633 silver badges7 bronze badges 5
  • What are "deleting destructor" and "complete object destructor"? – 3CxEZiVlQ Commented Mar 15 at 19:51
  • @3CxEZiVlQ Sorry,I pasted the wrong code before, but I've fixed it now. About "deleting destructor" and "complete object destructor", please see here. – xxllxx666 Commented Mar 15 at 19:54
  • I don't see _ZN4BaseD2Ev in main. But I can see it in main.o. godbolt./z/WMvGc3qxP In any case this is implementation details and not mandated by the standard. – 3CxEZiVlQ Commented Mar 15 at 20:00
  • _ZN4BaseD1Ev and _ZN4BaseD2Ev appear to have the same address, not sure why there are two symbols for the same thing. – catnip Commented Mar 15 at 20:19
  • 1 stackoverflow/a/44558248/1462718 eli.thegreenplace/2015/… – Brandon Commented Mar 15 at 21:09
Add a comment  | 

1 Answer 1

Reset to default 0

By using the g++ -S main.cpp to generate main.s, we can see that _ZN4BaseD1Ev is an alias for _ZN4BaseD2Ev. I believe this is a compiler optimization: in the absence of virtual base classes, the complete object destructor can reuse the implementation of the base object destructor, that is, both share the same code.

.size   _ZN4BaseD2Ev, .-_ZN4BaseD2Ev
.weak   _ZN4BaseD1Ev
.set    _ZN4BaseD1Ev,_ZN4BaseD2Ev
发布评论

评论列表(0)

  1. 暂无评论