跳转至

[C++] Destructor Order

导言

  • 一般来说,析构函数应该只处理释放资源,不处理逻辑。
  • 但是PTA的代码里,在全局变量的析构函数里,写了向子线程发送结束信号的函数,和childThread.join()。这导致了很奇怪的问题,string demalloc等。

为此,想研究一下C++的析构函数执行顺序。包括嵌套的Class结构,和全局变量的析构时机。

顺序结构

  • 全局变量是静态变量,是后构造的先析构。
  • Class的多个子类成员,其析构顺序是按声明顺序来执行的。
Class 内成员
class SubClass1 {
public:
    ~SubClass1() { std::cout << "SubClass1 Destructor\n"; }
};

class SubClass2 {
public:
    ~SubClass2() { std::cout << "SubClass2 Destructor\n"; }
};

class Class {
private:
    SubClass1 obj1;
    SubClass2 obj2;
public:
    ~Class() { std::cout << "Class Destructor\n"; }
};
// SubClass1 Destructor
// SubClass2 Destructor
// Class Destructor

嵌套结构

基本析构顺序是,内部先析构,再析构外部。

类的继承关系

当存在继承关系时,析构函数的调用顺序确实是先调用派生类的析构函数,再调用基类的析构函数。这样做的目的是确保派生类对象能够完全销毁自己在其析构函数中创建的资源,然后基类才会处理自己部分的资源。

以下是一个简单的例子来展示这种析构顺序:

示例代码:
#include <iostream>

class Base {
public:
    Base() { std::cout << "Base constructor\n"; }
    virtual ~Base() { std::cout << "Base destructor\n"; }
};

class Derived : public Base {
public:
    Derived() { std::cout << "Derived constructor\n"; }
    ~Derived() { std::cout << "Derived destructor\n"; }
};

int main() {
    std::cout << "Creating Derived object...\n";
    Derived d;  // 创建派生类对象
    std::cout << "Derived object created.\n";

    return 0;
}

输出结果:

Creating Derived object...
Base constructor
Derived constructor
Derived object created.
Derived destructor
Base destructor

解释:

  1. 派生类对象创建
  2. 创建 Derived 类型的对象时,首先会调用 基类 Base 的构造函数。
  3. 然后调用 派生类 Derived 的构造函数。

  4. 派生类对象销毁

  5. Derived 对象超出作用域时,首先会调用 派生类 Derived 的析构函数。
  6. 然后,派生类的析构函数结束后,才会调用 基类 Base 的析构函数。

关键点:

  • 析构函数调用顺序:基类的析构函数会在派生类析构函数之后被调用。这样做确保派生类可以完全处理自己特有的资源释放,基类的析构函数才会处理基类部分的资源。
  • 虚拟析构函数:在基类中声明析构函数为 virtual 是非常重要的,因为这样做能够确保在销毁派生类对象时,正确调用派生类和基类的析构函数。如果析构函数不是虚拟的,则可能会导致析构时基类的析构函数不被调用,从而引发内存泄漏或资源释放不完全的情况。

全局变量

  1. 静态变量的析构会在主线程结束时立刻执行。
  2. thread 变量并不会阻拦主线程的结束,和之后全局变量的析构; thread的变量的作用是通过thread.join()来阻塞主进程,如果不阻塞,则主线程析构到thread的变量时会发现子线程没结束,报错abort()
(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1  0x00007feab297a859 in __GI_abort () at abort.c:79
#2  0x00007feab2c038d1 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007feab2c0f37c in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007feab2c0f3e7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x000055c337f4b378 in std::thread::~thread (this=<optimized out>, __in_chrg=<optimized out>) at /usr/include/c++/9/thread:139
#6  main () at /home/t00906153-normal13/github/cpp-printer/tests/destructor.cpp:34

实例分析

PTA多线程实例解释

PTA多线程在结束时的行为是:

  • 主线程(python)已结束
  • 二级流水线程 在等待信号
  • Release线程 在轮询检查释放资源

按照前面的逻辑,这程序应该会正常结束:

  • 子线程等待主线程释放信号;
  • 主线程完成代码执行后析构,释放信号,并在join 处等待子线程。

实际情况是:

  • 对于未修改代码,主线程正常析构,释放信号,子线程正常结束。
  • 但是我加上了cpprinter后,主线程不会析构,也就不会释放信号,子线程就无限循环。
#0  0x00007f21211bdbbf in __GI___poll (fds=0x7f20d3190bc0, nfds=1, timeout=timeout@entry=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
#1  0x00007f207f5193e1 in select_poll_poll_impl (timeout_obj=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    self=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Modules/selectmodule.c:633
#2  select_poll_poll (self=0x7f20d36a3770, args=<optimized out>, nargs=<optimized out>) at /usr/local/src/conda/python-3.8.20/Modules/clinic/selectmodule.c.h:219
#3  0x000055ac31b92a0c in method_vectorcall_FASTCALL (func=<optimized out>, args=0x55ac3c100690, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/clinic/dictobject.c.h:74
#4  0x000055ac31b77446 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x55ac3c100690, callable=0x7f207f540a70) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#5  call_function (kwnames=0x0, oparg=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0)
    at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#6  _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3486
#7  0x000055ac31b75ddd in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:741
#8  _PyEval_EvalCodeWithName (_co=0x7f207f54a5f0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>, kwargs=0x55ac3c0ffa58, kwcount=<optimized out>, kwstep=1,
    defs=0x7f207f54b068, defcount=<optimized out>, kwdefs=0x0, closure=0x0, name=0x7f20802f5ea0, qualname=0x7f207f5497b0) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4298
#9  0x000055ac31b8685d in _PyFunction_Vectorcall (func=<optimized out>, stack=0x55ac3c0ffa48, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:436
#10 0x000055ac31b77446 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x55ac3c0ffa48, callable=0x7f207f551050) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#11 call_function (kwnames=0x0, oparg=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0)
    at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#12 _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3486
#13 0x000055ac31b75ddd in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:741
#14 _PyEval_EvalCodeWithName (_co=0x7f2115ab71e0, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>, kwargs=0x7f1f7e0639f0, kwcount=<optimized out>, kwstep=1,
    defs=0x7f2115ab66a8, defcount=<optimized out>, kwdefs=0x0, closure=0x0, name=0x7f2120d3ebd0, qualname=0x7f2120d3ebd0) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4298
#15 0x000055ac31b8685d in _PyFunction_Vectorcall (func=<optimized out>, stack=0x7f1f7e0639e0, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:436
#16 0x000055ac31b77113 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x7f1f7e0639e0, callable=0x7f2115abc910) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#17 call_function (kwnames=0x0, oparg=<optimized out>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#18 _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3500
#19 0x000055ac31b75ddd in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:741
#20 _PyEval_EvalCodeWithName (_co=0x7f1f7e075450, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>, kwargs=0x7f1f7e041d88, kwcount=<optimized out>, kwstep=1,
    defs=0x7f20d36a3298, defcount=<optimized out>, kwdefs=0x0, closure=0x0, name=0x7f207f5d5e50, qualname=0x7f20d36a26a0) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4298
#21 0x000055ac31b8685d in _PyFunction_Vectorcall (func=<optimized out>, stack=0x7f1f7e041d78, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:436
#22 0x000055ac31b77446 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x7f1f7e041d78, callable=0x7f1f7e077730) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#23 call_function (kwnames=0x0, oparg=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0)
    at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#24 _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3486
#25 0x000055ac31b75ddd in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:741
#26 _PyEval_EvalCodeWithName (_co=0x7f1f7e097040, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>, kwargs=0x7f1f7e05e1e8, kwcount=<optimized out>, kwstep=1,
    defs=0x7f1fa4868e78, defcount=<optimized out>, kwdefs=0x0, closure=0x0, name=0x7f2120d3ebd0, qualname=0x7f1f7e051ee0) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4298
#27 0x000055ac31b8685d in _PyFunction_Vectorcall (func=<optimized out>, stack=0x7f1f7e05e1d8, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:436
#28 0x000055ac31b77446 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x7f1f7e05e1d8, callable=0x7f1f7e0539b0) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#29 call_function (kwnames=0x0, oparg=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0)
    at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#30 _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3486
#31 0x000055ac31b75ddd in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:741
#32 _PyEval_EvalCodeWithName (_co=0x7f2115ae0e10, globals=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>, kwargs=0x7f1f64000d40, kwcount=<optimized out>, kwstep=1,
    defs=0x7f2115ae1ec8, defcount=<optimized out>, kwdefs=0x0, closure=0x0, name=0x7f2120d92540, qualname=0x7f2115ae3040) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4298
#33 0x000055ac31b8685d in _PyFunction_Vectorcall (func=<optimized out>, stack=0x7f1f64000d38, nargsf=<optimized out>, kwnames=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:436
#34 0x000055ac31b77446 in _PyObject_Vectorcall (kwnames=0x0, nargsf=<optimized out>, args=0x7f1f64000d38, callable=0x7f2115aeb190) at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:127
#35 call_function (kwnames=0x0, oparg=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>, pp_stack=<synthetic pointer>, tstate=0x55ac32904fe0)
    at /usr/local/src/conda/python-3.8.20/Python/ceval.c:4963
#36 _PyEval_EvalFrameDefault (f=<optimized out>, throwflag=<optimized out>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:3486
#37 0x000055ac31c0c8cf in PyEval_EvalFrameEx (throwflag=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>,
    f=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Python/ceval.c:738
#38 function_code_fastcall (co=<optimized out>, args=<optimized out>, nargs=5, globals=<optimized out>) at /usr/local/src/conda/python-3.8.20/Objects/call.c:284
#39 0x000055ac31b945aa in PyVectorcall_Call (kwargs=0x0, tuple=0x7f2120d82050, callable=0x7f2115a9aa50) at /usr/local/src/conda/python-3.8.20/Objects/dictobject.c:1802
#40 PyObject_Call (callable=0x7f2115a9aa50, args=0x7f2120d82050, kwargs=0x0) at /usr/local/src/conda/python-3.8.20/Objects/call.c:228
#41 0x000055ac31c46ae8 in atexit_callfuncs (module=<optimized out>) at /usr/local/src/conda/python-3.8.20/Modules/atexitmodule.c:87
#42 0x000055ac31c2d6be in call_py_exitfuncs (istate=0x55ac32903d20) at /usr/local/src/conda/python-3.8.20/Python/pylifecycle.c:2235
#43 Py_FinalizeEx () at /usr/local/src/conda/python-3.8.20/Python/pylifecycle.c:1182
#44 0x000055ac31af4bf2 in Py_Exit (sts=0) at /usr/local/src/conda/python-3.8.20/Python/pylifecycle.c:2294
#45 0x000055ac31af4b55 in handle_system_exit () at /usr/local/src/conda/python-3.8.20/Python/pythonrun.c:684
#46 0x000055ac31af47f8 in _PyErr_PrintEx (tstate=0x55ac32904fe0, set_sys_last_vars=1) at /usr/local/src/conda/python-3.8.20/Python/pythonrun.c:694
#47 0x000055ac31afcd29 in pyrun_simple_file (flags=0x7ffd3afa2688, closeit=1, filename=0x7f2120d7d280, fp=0x55ac329031d0) at /usr/local/src/conda/python-3.8.20/Python/pythonrun.c:445
#48 PyRun_SimpleFileExFlags (fp=0x55ac329031d0, filename=<optimized out>, closeit=1, flags=0x7ffd3afa2688) at /usr/local/src/conda/python-3.8.20/Python/pythonrun.c:472
#49 0x000055ac31af102e in pymain_run_file (cf=0x7ffd3afa2688, config=0x55ac32903dd0) at /usr/local/src/conda/python-3.8.20/Modules/main.c:389
#50 pymain_run_python (exitcode=<error reading variable: dwarf2_find_location_expression: Corrupted DWARF expression.>) at /usr/local/src/conda/python-3.8.20/Modules/main.c:618
#51 Py_RunMain () at /usr/local/src/conda/python-3.8.20/Modules/main.c:697
#52 0x000055ac31bffd27 in Py_BytesMain (argc=<optimized out>, argv=<optimized out>) at /usr/local/src/conda/python-3.8.20/Modules/main.c:1117
#53 0x00007f21210cf083 in __libc_start_main (main=0x55ac31bffce0 <main>, argc=5, argv=0x7ffd3afa2898, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffd3afa2888) at ../csu/libc-start.c:308
#54 0x000055ac31bffc3a in _start () at /usr/local/src/conda/python-3.8.20/Include/cpython/abstract.h:128

主线程卡在python的调用栈里,没有触发析构,

析构函数里添加CPPRINTER

会有如下各种随机:

  • string seg fault
  • filesystem abort
  • std::cerr 打印5000+行的未知数据,看输出应该,原本要打印的static变量被释放了,把栈信息打印出来了。

  • 猜测原因是CPPRINTER写的比较复杂,启动析构时环境已经开始销毁了,导致各种随机错误。

  • 解决方案(测试):析构函数里调用简单的,不涉及static变量的函数。

多方测试

由于陷入了矛盾的困境,需要小实验来验证。

多线程时,子线程循环调用全局变量时,全局变量析构的时机

???

参考文献

评论