C++ disable destructors for static variables

2019-05-06 17:30发布

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.

标签: c++ embedded
4条回答
对你真心纯属浪费
2楼-- · 2019-05-06 17:57

A simple solution is to use placement new - instantiating the objects on appropriately sized static arrays. You can also use a reference variable to access the objects via the instance rather than the 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 ;

In placement objects, the destructor must be explicitly called so you have complete control over whether and when it is called.

查看更多
Luminary・发光体
3楼-- · 2019-05-06 18:08

Just use a reference to a heap allocated variable. It will leak, but I guess that's what you want.

static A& a = *(new A);
查看更多
We Are One
4楼-- · 2019-05-06 18:11

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(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.

查看更多
虎瘦雄心在
5楼-- · 2019-05-06 18:17

In a bare-metal embedded system you normally have access to the run-time start-up code (usually in assembler); this code performs global static initialisation including calling constructors before calling main(). It also determines what happens if main() terminates; that is where static destructors will be called - this code can be removed (if it even exists already), so that the destructor is not explicitly called on termination - this may allow linker optimisation to then remove the unused code.

You should check the map file to determine whether the destructor is included in the build rather than looking at the compiler assembler output - the compiler has no option but to generate the code since it does not know whether it will be externally referenced or not. You may need to set specific linker options to remove unused code.

查看更多
登录 后发表回答