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
|
1 Answer
Reset to default 0By 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
_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