4

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.

Allan
  • 4,562
  • 8
  • 38
  • 59
  • 2
    You cannot "disable" destructors for static objects on program exit. But you may allocate them via `new` and use static pointers... – Alex Shesterov Dec 27 '14 at 21:17
  • I added a note on that it does not need to be portable - do not know if that changes any thing. Should have been clear on that from the beginning. – Allan Dec 27 '14 at 21:21
  • There is a trick to make static objects not call a destructor on exit, but I don't know if it prevents destructor code from being generated and put in the binary for those classes anyway, even if there are no non-static objects. – krojew Dec 27 '14 at 21:33
  • @krojew could you let us in on the details of this "trick", then we might be able to find out if this can work. – Allan Dec 27 '14 at 21:37
  • @Allan take a look at http://ideone.com/ndDZhT - I made a wrapper which you can use on static objects to turn off their destructors. If that's what you wanted, I'll post an explanation of how it works. Of course that's a simple example even without an access to the data, just to give an idea. – krojew Dec 27 '14 at 21:49
  • I checked the generated assembler code and there is no trace of destructor. If you think this might be an answer, I'll post something more functional, rather than illustrative. – krojew Dec 27 '14 at 22:05
  • I did play around with it and do find it interesting (http://goo.gl/7tGHjc) as it seem to solve the issue with the destructor code, but I can not figure out how to make it a "drop-in-replacement"... I need some way to call member functions of variables instantiated with "StaticWrapper". This can be solved by overloading the dereference operator (->) but this will involve some refactoring. Anyway, please post this as an answer. – Allan Dec 27 '14 at 22:22
  • @Allan for objects with empty destructor (e.g. `struct X { int x; };`) is the annoying code still generated? – M.M Dec 28 '14 at 11:53

4 Answers4

3

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.

krojew
  • 1,297
  • 15
  • 38
  • 2
    I think he wants to prevent the destructor from being included in the build rather than simply preventing it from being called. – Clifford Dec 27 '14 at 22:49
  • We already checked this answer in the comments - the destructor is left out of the code. – krojew Dec 27 '14 at 22:50
  • But what if you have a destructor that actually *does something*? – Clifford Dec 28 '14 at 08:03
  • I think in such case it really should be called, otherwise who knows what resources would leak. I assume Allan is aware of that. – krojew Dec 28 '14 at 08:07
  • I think the idea is that in a bare-metal embedded system, the process *never* terminated, so the destructor will never be called when all instances are static. – Clifford Dec 28 '14 at 09:55
  • In this case the answer will prevent the destructor from being called and avoid the extra binary code associated with it. – krojew Dec 28 '14 at 10:20
2

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);
Photon
  • 3,182
  • 1
  • 15
  • 16
  • 1
    I believe the requirement is to not include the destructor in the binary in order to save flash memory space rather than simply to prevent it from being called. – Clifford Dec 27 '14 at 22:47
  • Strange, the original question says that when this class is used as a regular variable, the destructor still needs to be called. How can you then remove it from the object code. Question is probably not clear enough. – Photon Dec 28 '14 at 06:37
  • @Clifford the destructor must be included in the binary because the object is used as an automatic variable sometimes. What OP wants is to not have the binary contain the code to invoke the destructor for static instances during program termination – M.M Dec 28 '14 at 11:49
  • 1
    note: this changes from static allocation to dynamic (heap) allocation. Could be an issue – M.M Dec 28 '14 at 11:50
  • @MattMcNabb : "somtimes" but not necessarily in the same application build - the question is ambiguous, but the stated aim is to optimise flash memory usage. Excluding the destructor from static objects will save nothing if there are also non-static instances. Unless in-lined there will only be a single copy of the destructor code for all instances automatic. – Clifford Dec 28 '14 at 20:40
  • 1
    @MattMcNabb : Heap allocation could be avoided by using *placement new* on a statically allocated array `char object_memory[sizeof(A)]`. – Clifford Dec 28 '14 at 21:00
1

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.

Clifford
  • 88,407
  • 13
  • 85
  • 165
1

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.

Clifford
  • 88,407
  • 13
  • 85
  • 165