C ++ disable destructors for static variables

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

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

Below is an example to illustrate the problem. A

is a class where a destructor is needed for normal circumstances, but not needed for static variables.

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

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

static A a;
static A b;

      

Below is the assembler code generated (compiled with -Os -std=c++11 -fno-exceptions -fno-rtti

) generated: 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 you can see from the assembler code above, enough instructions are being issued to make this cleanup code.

Is there anything I can do to disable this unnecessary cleanup code? This doesn't need to be portable - as long as it works in recent versions of GCC. Attributes, linker scripts, modifying object files, and other tricks are generally welcome.

+3


source to share


4 answers


The answer is to create 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(T) int8_t mData[sizeof(T)];
};

      

This wrapper can be used to wrap classes that shouldn't call the destructor:



struct A
{
    int i;
};

static StaticWrapper<A> a;
a->i = 1;

      

The way it works is we store (statically) some memory large enough to contain the correctly aligned object. We then use the new in-place operator to create a real object in the reserved memory and pass potential arguments to its constructor. We can access the object from our shell using the → operator. The destructor will never be called because from the compiler's point of view there is no object of class T anywhere - just an array of bytes. We just use these bytes to store the object.

+2


source


In a white metal embedded system, you usually have access to the startup code at startup time (usually in assembler); this code does global static initialization, including calling constructors, before calling main()

. It also determines what happens if it main()

exits; that is, where the static destructors will be called - that code can be removed (if it already exists) so that the destructor is not explicitly called on completion - this can allow linker optimization and then remove unused code.



You have to check the map file to determine if the destructor is included in the assembly, rather than look at the compiler's assembler output - the compiler has no option other than to generate code as it doesn't know if it will reference external references or not. You may need to set certain linker options to remove unused code.

+1


source


Just use a heap allocated variable reference. It will leak, but I think what you want.

static A& a = *(new A);

      

0


source


A simple solution is to use placement new - instantiating objects on static arrays of the appropriate size. You can also use a reference variable to access objects through an instance rather than a pointer.

#include <new>

static char mem_for_a[sizeof(A)] ;
static A* aptr = new(mem_for_a) A ;
static A& a = *aptr ;

static char mem_for_b[sizeof(A)] ;
static A* bptr = new(mem_for_b) A ;
static A& b = *bptr ;

      

On placement objects, the destructor must be explicitly called so that you have full control over whether it will be called.

0


source







All Articles