禁用删除析构函数的生成

Disable generation of deleting destructor

提问人:jiwopene 提问时间:9/3/2023 更新时间:9/15/2023 访问量:95

问:

我正在为嵌入式系统开发一个不使用动态内存分配的程序。如何防止 GCC 生成删除析构函数(名称中带有损坏的析构函数)?它永远不会被调用。D0

我认为甚至没有必要删除析构函数,因为完整的对象析构函数(以残缺的名称)可以在它之后调用。D1operator delete(…)

在我看来,应该有一些命令行选项来禁用这些析构函数。


主要问题是程序调用删除析构函数。由于我没有任何堆管理,因此没有定义这样的函数。作为一种解决方法,我可以只报告错误,但这会浪费内存(不必要的大程序大小),并且不允许我在编译过程中捕获意外调用。operator delete(…)operator delete(…)operator delete()

C++ GCC 嵌入式 析构函数独立

评论

0赞 user17732522 9/3/2023
"因为完整的对象析构函数(名称为 D1)可以与运算符 delete(...) 一起调用。这就是删除析构函数存在的原因。delete
0赞 jiwopene 9/3/2023
@user17732522,关于内联 destrucotrs 的事情很好。如果它甚至适用于虚拟析构函数,请将其作为答案发布。我会尝试的。
0赞 JaMiT 9/3/2023
“在我看来,应该有 [X]”——gcc 确实接受贡献......
0赞 jiwopene 9/3/2023
@JaMiT,这件事让我考虑将此功能作为新的 CLI 选项添加到 GCC,除非现在有办法做到这一点。这会导致它生成非标准(和不兼容)的目标文件,但对于嵌入式软件开发中的某些情况,它听起来非常有用。
0赞 user17732522 9/3/2023
@jiwopene哎呀,我的思维过程有点不对劲。即使是内联的,编译器当然也会在创建类类型的完整对象后创建删除析构函数。这是必要的,因为在另一个看不到内联定义的翻译单元中,它可能有一个基础。delete

答:

5赞 user17732522 9/3/2023 #1

存在删除析构函数变体,以便语法

delete ptr;

即使不指向派生最多的对象,指向多态类型的 where 也可以工作。的用户不知道最派生对象的偏移量或其大小是多少,但它需要知道这一点才能正确调用。因此,需要对知道的函数进行间接/虚拟调用,该函数是删除析构函数。ptrptrdelete ptr;operator delete

不幸的是,编译器必须从这样的析构函数生成删除析构函数,至少对于用作大多数派生对象的所有类,因为在另一个翻译单元中可能存在这种表达式,该表达式不知道这个最派生的析构函数的定义,但它必须(间接)调用。virtualdelete


我不认为您可以将虚拟析构函数的行为与执行显式虚拟析构函数调用的能力分开,并且我也没有看到任何 GCC 开关来禁用删除析构函数的生成作为非标准/ABI 符合选项。delete

所以我想你必须避免使用虚拟析构函数。无论如何,您都可以通过从函数转发来获取虚拟销毁行为:virtual

struct Base {
    virtual destroy() noexcept { this->~Base(); }
    // destructor not virtual
};

struct Derived {
    virtual destroy() noexcept override { this->~Derived(); }
};

然后你可以用 .ptr->~Base();std::destroy_at(ptr)ptr->destroy()

但是,这有一个问题,您需要确保在每个派生类中正确覆盖该类,以避免未定义的行为。CRTP 或 C++23 显式对象参数(显式)可能会有所帮助。destructthis

您还存在一个问题,即有人可能会不小心直接调用析构函数,从而再次导致未定义的行为。创建析构函数通常也不是一种解决方案,因为析构函数在许多情况下可能会被调用,例如,在包含相关类作为非静态成员的类的构造函数中。private


我最初建议在这里实现 as 空,并在每个翻译单元中帮助删除调用,但是我没有意识到将可替换的释放函数标记为使程序 IFNDR 等无效。(参见 [replacement.functions]/3operator deleteinlineinline

您无法删除 ,因为出于我上面提到的原因,它将被 odr 使用,因此必须可行。operator delete


LTO可能进一步帮助在链接过程中摆脱未使用的发出的删除析构函数,至少通过去虚拟化。但我没有测试过。