Search code examples
c++gccclang

Tell compiler not to execute destructor on my __thread variable


I specifically want no constructors and no destructors on one of my variables. It's defined as this

struct Dummy { ~Dummy() { assert(0); } };
__thread Dummy dumb;

The real code is zero initialized and grabs memory out of the thread local memory pool. The pool is destroyed at the end of main so this destructor tries to access a freed memory pool.

I can work around the problem but I wanted to do this in a clean way. 1) Can I tell the compiler to free my pool after all thread local (or global) variables? Or have it initialized before all thread local functions so it's cleaned up at the proper time? Or better yet 2) Not execute the cleanup at all? I could have sworn __thread doesn't allow constructors/destructors so I was surprised this even ran.

The stack trace shows __run_exit_handlers being the function that calls the destructor. It parents are exit, __libc_start_main and start. This occurs on both clang and gcc


Solution

  • Before I get into the answer, anyone reading this should know that this is generally a very bad idea, and should be treated as a last resort. However, what the OP wants is feasible, so here it is:

    Destructors are possibly the single most reliable thing in the C++ language. So if you want to prevent one from being executed, you'll have to manage the object's lifecycle and memory by yourself.

    We could just new the Dummy object and never delete it, but that would leak heap memory, which is not desirable. Instead, we want to create, but never destroy, the Dummy object in the memory associated with the thread-local object itself.

    For this, the way to go is std::aligned_storage and placement new:

    struct Dummy { ~Dummy() { assert(0); } };
    
    struct DummyWrapper {
      DummyWrapper() {
        // Create the dummy
        dummy_ptr = new (&dummy_data) Dummy();
      }
    
      // Intentionally leave the destructor defaulted-out
      ~DummyWrapper() = default;
    
      operator Dummy&() { return *dummy_ptr; }
    
      std::aligned_storage_t<sizeof(Dummy), alignof(Dummy)> dummy_data;
      Dummy* dummy_ptr;
    };
    
    __thread DummyWrapper dumb;
    

    The DummyWrapper object will be constructed and destroyed, there's no going around that. However, the Dummy object is only ever created, and never destroyed, but the underlying memory management is still kept sane.