You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

gdb与objdump显示的C++析构函数不一致问题排查

G++析构函数调用差异问题解析

测试代码

#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;
}

编译命令

g++ -g -O0 -fno-inline -std=c++11 -Wall main.cpp -o main

objdump输出的汇编结果

执行objdump -S -t main得到:

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>

此处显示删除析构函数_ZN4BaseD0Ev调用了完整对象析构函数_ZN4BaseD1Ev。

GDB调试结果

反汇编_ZN4BaseD0Ev

(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.

此处显示_ZN4BaseD0Ev调用的是基类对象析构函数_ZN4BaseD2Ev,且后续会调用operator delete。

查看虚表

(gdb) x/4ag 0x555555557d78 - 16
0x555555557d68 <_ZTV4Base>:     0x0     0x555555557d88 <_ZTI4Base>
0x555555557d78 <_ZTV4Base+16>:  0x555555555230 <_ZN4BaseD2Ev>   0x555555555250 <_ZN4BaseD0Ev>

虚表中存放的是_ZN4BaseD2Ev而非_ZN4BaseD1Ev。

核心问题

  1. 为什么objdump和GDB显示的调用目标不一致?
  2. _ZN4BaseD0Ev实际调用的是_ZN4BaseD1Ev还是_ZN4BaseD2Ev?

问题解析

首先明确G++为带虚析构的类生成的三个析构函数的作用:

  • _ZN4BaseD2Ev(基类对象析构函数):负责执行用户编写的析构逻辑,是所有析构函数的基础实现,无论对象是作为基类子对象还是完整对象,最终都会调用这个函数完成资源释放。
  • _ZN4BaseD1Ev(完整对象析构函数):仅针对完整对象(无继承场景下,和D2逻辑完全一致;有子类时,会先调用子类析构,再调用D2)。
  • _ZN4BaseD0Ev(删除析构函数):除了执行析构逻辑,还会调用operator delete释放对象内存,通常在delete表达式中被调用。

差异原因

你的代码中Base没有子类,此时D1和D2的实现逻辑完全相同。G++会做COMDAT符号折叠优化:将逻辑完全一致的D1和D2合并为同一个函数体,只是保留两个不同的符号名。

  • objdump直接解析可执行文件的静态符号表,显示的是编译时生成的调用目标符号D1,但实际该符号对应的地址和D2完全相同。
  • GDB在动态调试时,会根据内存地址对应的符号名显示,此时地址指向的符号是D2,所以显示调用D2。

实际调用情况

_ZN4BaseD0Ev实际调用的是同一个函数体——因为D1和D2被合并了,无论显示的是哪个符号,执行的都是你编写的析构函数逻辑(释放buf_),之后D0再调用operator delete完成内存释放。

你可以通过nm main | grep _ZN4BaseD验证:会发现_ZN4BaseD1Ev和_ZN4BaseD2Ev的地址完全相同,证明它们是同一个函数的不同符号别名。

环境信息

$ 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

内容的提问来源于stack exchange,提问作者xxllxx666

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 20:53:20