c++embedded

C++ disable destructors for static variables


I have a general purpose class which is used in different contexts - sometime as static variable, and sometime as a normal variable on the stack/heap.

When it is used as a normal variable the destructor must be called when it goes out of scope - as normal. The executable is used in an embedded target where flash is a limited resource and which will never exit, and for this I would like this "exit" code to be disabled.

Following is an example to illustrate the problem. A is the class where the destructor is needed for normal circumstances, but is not needed for static variables.

struct Abstract {
  virtual ~Abstract() {}
};

struct A : public Abstract {
  int i = 0;
};

static A a;
static A b;

Following is the assembler code generated (compiled with -Os -std=c++11 -fno-exceptions -fno-rtti) generated by: http://goo.gl/FWcmlu

Abstract::~Abstract():
    ret
A::~A():
    ret
A::~A():
    jmp operator delete(void*)
Abstract::~Abstract():
    jmp operator delete(void*)
    pushq   %rax
    movl    $__dso_handle, %edx
    movl    a, %esi
    movl    A::~A(), %edi
    call    __cxa_atexit
    popq    %rcx
    movl    $__dso_handle, %edx
    movl    b, %esi
    movl    A::~A(), %edi
    jmp __cxa_atexit
vtable for Abstract:
vtable for A:
b:
    .quad   vtable for A+16
    .long   0
    .zero   4
a:
    .quad   vtable for A+16
    .long   0
    .zero   4

As seen in the assembler code above a fair amount of instructions is issued to do this clean up code.

Is there anything which can be done to disable this unneeded cleanup code? It does not need to portable - as long as it works in recent versions of GCC. Attributes, linker scripts, altering the object files, and other tricks are mostly welcome.


Solution

  • The answer is by creating a wrapper:

    template<class T>
    class StaticWrapper
    {
    public:
        using pointer = typename std::add_pointer<T>::type;
    
        template<class... Args>
        StaticWrapper(Args && ...args)
        {
            new (mData) T(std::forward<Args>(args)...);
        }
    
        pointer operator ->()
        {
            return reinterpret_cast<pointer>(mData);
        }
    
    private:
        alignas(alignof(T)) int8_t mData[sizeof(T)];
    };
    

    This wrapper can be used to wrap classes which destructor should not be called:

    struct A
    {
        int i;
    };
    
    static StaticWrapper<A> a;
    a->i = 1;
    

    The way it works is - we reserve (statically) some memory big enough to contain the object with proper alignment. Then we use an in-place new operator to create the actual object in the reserved memory and forward potential arguments to its constructor. We can access the object from our wrapper using operator ->. The destructor will never be called because, from the compiler perspective, there is no object of class T anywhere - only an array of bytes. We just use those bytes to hold the object.