3

Well, my colleague is pretty in depth nitpicking about eliminating unnecessarily code instantiations for destructor functions. Still same situation, as mentioned in this question:

  • Very limited space for .text section (less 256 KB)
  • Code base should scale among several targets, including the most limited ones
  • Well known use cases of the code base by means some destructor logic is neccesary to manage object lifetimes or not (for many cases life-time of objects is infinite, unless the hardware is reset)

We have a target, that's very limited by means of .text section space available.
Unfortunately the GCC compiler will apparently instantiate code for even non virtual destructors, that actually have no side effects.

To get rid of these instantiatons he came up with the following wrapper construct, to eliminate the useless destructor calls:

The wrapper class:

#include <iostream>
#include <cstdint>
#include <utility>

template <typename T>
struct noop_destructor {
public:
    typedef T* pointer;
    typedef const T* const_pointer;
    typedef T& reference;
    typedef const T& const_reference;

    template<typename... Args>
    noop_destructor(Args&&... args) {
        new (reinterpret_cast<void*>(wrapped_data)) T(std::forward<Args>(args)...);
    }

    explicit noop_destructor(const noop_destructor& rhs) {
        std::copy(rhs.wrapped_data,rhs.wrapped_data+sizeof(T),wrapped_data);
    }

    noop_destructor& operator=(const noop_destructor& rhs) {
        std::copy(rhs.wrapped_data,rhs.wrapped_data+sizeof(T),wrapped_data);
        return *this;
    }

    pointer operator->() {
        return reinterpret_cast<pointer>(wrapped_data);
    }
    const_pointer operator->() const {
        return reinterpret_cast<const_pointer>(wrapped_data);
    }
    reference operator*() {
        return *reinterpret_cast<pointer>(wrapped_data);
    }
    const_reference operator*() const {
        return *reinterpret_cast<const_pointer>(wrapped_data);
    }
private:
    uint8_t wrapped_data[sizeof(T)] __attribute__ ((aligned (__BIGGEST_ALIGNMENT__)));
};

A wrapped class (just ignore the side effect of calling std::cout << ... in the destructor, the real life classes in question to be wrapped, have just empty, non virtual destructors)

class A {
public:
    A() : x_() {}
    A(int x) : x_(x) {}
    ~A() {
        std::cout << "noop destructor call of class A" << std::endl;
    }
    void foo() {
        std::cout << "x_ = " << x_ << std::endl;
    }

private:
    int x_;
};

Some instantiations to demonstrate behavior:

int main() {
    A a1(5);
    noop_destructor<A> a2;

    a1.foo();
    (*a2).foo();
    return 0;
}

The approach just works fine (as you can see from the above code sample).

What I actually don't like about it, is you have no proof if a class actually can be wrapped with noop_destructor<>, and there's no indicator if T's destructor is actually empty, and can be safely eliminated or not.

Does anyone have an idea, how to make this more safe from a semantical level?

Community
  • 1
  • 1
πάντα ῥεῖ
  • 83,259
  • 13
  • 96
  • 175
  • It might be worth trying out the `gold` linker with the `--icf` option, to see if at least you can cut it down to one no-op destructor. – Sneftel Mar 06 '15 at 23:56
  • 1
    Why don't you use `std::aligned_storage` for your storage? – Kerrek SB Mar 06 '15 at 23:57
  • 4
    The relevant trait should be [`std::is_trivially_destructible`](http://en.cppreference.com/w/cpp/types/is_destructible). – Kerrek SB Mar 06 '15 at 23:58
  • 1
    The class `noop_destructor` also has an empty destructor. What happens to the invocation of that. – Cheers and hth. - Alf Mar 06 '15 at 23:58
  • @KerrekSB I just didn't knew better than using the attribute shown, sorry :P ... – πάντα ῥεῖ Mar 06 '15 at 23:58
  • @Cheersandhth.-Alf _"What happens to the invocation of that."_ Nothing, that was the actual goal :) And as far we could see, there was no instantiation at all. – πάντα ῥεῖ Mar 06 '15 at 23:59
  • 3
    But how is that empty destructor different from the empty destructor of the wrapped class? – Cheers and hth. - Alf Mar 06 '15 at 23:59
  • @Cheersandhth.-Alf There's no single bit of code instantiated for these, in contrast to the others. – πάντα ῥεῖ Mar 07 '15 at 00:01
  • 6
    Hm. I think I would try to apply whole-program optimization. And just various optimization options. And possibly think about using other tools. It should not be necessary to create such things to do the compiler's job for it. – Cheers and hth. - Alf Mar 07 '15 at 00:03
  • @KerrekSB `std::is_trivially_destructible` sounds promising. I'll have a deeper look. – πάντα ῥεῖ Mar 07 '15 at 00:04
  • 2
    Alf's point is valid IMHO. I'm curious what the actual problem is. _"Unfortunately the GCC compiler will apparently instantiate code for even non virtual destructors, that actually have no side effects."_ Can you show a demonstration of this? Are you enabling optimisation? Are they defined inline? – Jonathan Wakely Mar 07 '15 at 00:11
  • 2
    @Cheersandhth.-Alf's point is legit. This is something the compiler should do. I just tried gcc with -O2 -S, and a blank destructor is no where to be found in the assembly code output. This is as we would expect from gcc. Note, however, that without -O2, it does generate a blank destructor. – thang Mar 07 '15 at 00:15
  • @JonathanWakely _"Can you show a demonstration of this?"_ Well, a bit hard to show here (on the fly now). But we've checked everything with results of `nm` on te final artifact. Yes, useless inline instantiations (just preserving stack frame instantiations) seem to appear in assembly code. We're on ARM assembly, if this matters somehow. – πάντα ῥεῖ Mar 07 '15 at 00:16
  • 1
    You could use http://gcc.godbolt.org/ ... what you describe should not happen. If it does, you should report a GCC bug. – Jonathan Wakely Mar 07 '15 at 00:17
  • @JonathanWakely I don't actually see, why these noop destructors are instantiated in code, could as well be a flaw of the GCC linker implementation. – πάντα ῥεῖ Mar 07 '15 at 00:19
  • 2
    Yes, but I find it very surprising that noone else using GCC on ARM has noticed this and complained. You're hardly the first person to compile for ARM! (There is no "GCC linker", and the empty inline calls you describe should never even be emitted in the assembly, so the linker should be irrelevant). – Jonathan Wakely Mar 07 '15 at 00:20
  • @JonathanWakely Well, my co-worker complained, and found that mentioned solution elsewhere. I also never heard about this behavior, had a hard time to work in understanding, what he's actually trying to do, and finally came up with this question here (SO is really one of my last resorts for asking after doing research). – πάντα ῥεῖ Mar 07 '15 at 00:24
  • 7
    I just tried with ARM androideabi and ARM gnueabi, and both show exactly the same behavior as what I mentioned in my previous comment. namely, no blank destructor with -O2, and this is not the case without -O2. Tell your co-worker he's an idiot. He's probably trying to show off what he thinks is fancy c++ syntax.... I've worked with a lot of programmers like that. – thang Mar 07 '15 at 00:27
  • @ethang I can't exactly tell, but I think the issue comes up with GCC non-eabi compiler versions. – πάντα ῥεῖ Mar 07 '15 at 00:30
  • 7
    I think you should verify exactly what your colleague is doing, and make sure they are using a sensible optimisation level for a small device (try both -O2 and -Os). These second-hand questions involving ugly workarounds for unverifiable claims someone else has made are not very useful for other SO users :) – Jonathan Wakely Mar 07 '15 at 00:30
  • @ethang I certainly won't call him an idiot (though I have my doubt's what he's actually doing). He's a highly intelligent, and well versed C-programmer learning C++ now, and looking at things analyzing the stuff at assembly level (and what the footprint results are and why). – πάντα ῥεῖ Mar 07 '15 at 00:33
  • I just tried with arm none eabi, and the result is exactly the same. I didn't dig into it, but I think these arm gcc packages are forked from the same tree. There's no reason to expect anything different. – thang Mar 07 '15 at 00:35
  • Why don't you simply delete the empty destructors or make them `= default;`? That makes them trivial (which an empty user defined dtor is not) and the compiler should have more options to elliminate them. – Jesper Juhl Mar 23 '16 at 16:41
  • This sounds like a linker issue. A modern linker can remove unused symbols. But it does not always do it (what if resulting module is linked dynamically from other module and the destructor is called (non-inlined) from there?). Please check [this question](http://stackoverflow.com/questions/6687630/how-to-remove-unused-c-c-symbols-with-gcc-and-ld), maybe it would help. I usually use MSVC on x86, and it also has separate compiler switches for removing unused functions from executable. – stgatilov Jan 10 '17 at 17:20

0 Answers0