2

I am developing a program for embedded system that will not use dynamic memory allocation. How can I prevent the GCC from producing deleting destructor (the destructor with D0 in its mangled name)? It will be never called.

I think that there is even no need to have deleting destructors, since the complete object destructor (D1 in mangled name) can be called instead along with operator delete(…) after it.

In my opinion, there should be some command-line option to disable these destructors.


The main problem is that the program calls operator delete(…) in the deleting destructor. Since I do not have any heap management, no such function is defined. As a workaround, I could implement operator delete(…) that just reports an error, but this would waste memory (unnecessarily large program size) and will not allow me to catch accidental call to operator delete() during the compilation.

jiwopene
  • 3,077
  • 17
  • 30
  • "_since the complete object destructor (D1 in mangled name) can be called instead along with operator delete(…) after it._": No, the caller doesn't know the pointer offset to or size of the most-derived object when `delete` is applied to a base class pointer. That's why the deleting destructor exists. – user17732522 Sep 02 '23 at 18:12
  • @user17732522, that thing about inline destrucotrs is nice. If it applies even to virtual destructors, post it as an answer. I will try that. – jiwopene Sep 02 '23 at 18:16
  • *"In my opinion, there should be [X]"* -- gcc does accept contributions... – JaMiT Sep 02 '23 at 18:21
  • @JaMiT, this thing made me think about adding this feature to the GCC as a new CLI option, unless there is a way to do it now. It would cause it to produce non-standard (and non-compatible) object files, but it sounds very useful for some cases in embedded software development. – jiwopene Sep 02 '23 at 18:23
  • @jiwopene Oops, my thought process was a bit wrong there. Even if inline the compiler will of course create the deleting destructor once you create a complete object of the class type. It's necessary since there could be a `delete` on it to one of its bases in another translation unit which doesn't see the inline definition. – user17732522 Sep 02 '23 at 18:25
  • I don't think you can avoid having them. I'd suggest defining `operator delete` as a noop and inline in every translation unit. Then GCC should inline it at least. By the way, how are you using virtual destructors without `delete`? How do you know to call the correct type's destructor on a polymorphic pointer? – user17732522 Sep 02 '23 at 18:26
  • @user17732522, the polymorphic types are allocated using placement new. No `operator new` nor `operator delete` calls are done and I would like to avoid them so I do not need to introduce any heap management just because of few instances of these classes. – jiwopene Sep 02 '23 at 18:30
  • I can perform some workaround very easily, but I am looking for an elegant solution. – jiwopene Sep 02 '23 at 18:30
  • Good point. Maybe that changing the design could solve the problem/clumsiness. – jiwopene Sep 02 '23 at 18:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/255151/discussion-between-jiwopene-and-user17732522). – jiwopene Sep 02 '23 at 18:33

1 Answers1

3

The deleting destructor variant exists so that the syntax

delete ptr;

where ptr points to an polymorphic type can work even if ptr doesn't point to the most-derived object. The user of delete ptr; doesn't know what the offset to the most-derived object or its size is, but it needs to know that in order to call operator delete correctly. Therefore there needs to be an indirect/virtual call to a function that knows, which is the deleting destructor.

Unfortunately the compiler has to generate the deleting destructor from such a virtual destructor at least for all classes which are used as most-derived objects, since there could be a delete expression of that kind in another translation unit that doesn't know about the definition of this most-derived destructor which it must however (indirectly) call.


I don't think that you can separate the delete behavior of virtual destructors from the ability to do explicit virtual destructor calls and I also don't see any GCC switch to disable the generation of the deleting destructor as a non-standard/ABI conforming option.

So I guess you'll have to avoid virtual destructors. You can get the virtual destruction behavior anyway by forwarding from a virtual function:

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

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

And then instead of ptr->~Base();/std::destroy_at(ptr) you can use ptr->destroy().

However, this has the problem that you need to assure that destruct is properly overriden in every derived class to avoid undefined behavior. CRTP or C++23 explicit object parameters (explicit this) may help with that.

You also have the problem that someone might accidentally call the destructor directly, again causing undefined behavior. Making the destructor private is also generally not a solution because the destructor is potentially invoked in many situations, e.g. in a constructor of a class that contains the class in question as a non-static member.


Alternatively I'd suggest defining operator delete(void*, std::size_t) as inline and a noop in every translation unit. That way GCC seems to at least inline it into the deleting destructor.

You can't delete the operator delete, because it will be odr-used for the reasons I mentioned above and must therefore viable.


LTO might further help getting rid of unused emitted deleting destructors during linking, at least via devirtualization. But I haven't tested.

user17732522
  • 53,019
  • 2
  • 56
  • 105