├── 0xabad1dea.cpp ├── 0xabad1dea.h └── README.md /0xabad1dea.cpp: -------------------------------------------------------------------------------- 1 | #include "0xabad1dea.h" 2 | #include 3 | 4 | // Change this to which ever locking primitive you use if need be 5 | #ifndef NOTHREADSAFE 6 | #include 7 | struct StaticLock { 8 | StaticLock() { pthread_mutex_init(&m_mutex, nullptr); } 9 | ~StaticLock() { pthread_mutex_destroy(&m_mutex); } 10 | void lock() { pthread_mutex_lock(&m_mutex); } 11 | void unlock() { pthread_mutex_unlock(&m_mutex); } 12 | private: 13 | pthread_mutex_t m_mutex; 14 | }; 15 | #else 16 | struct StaticLock { 17 | void lock() { } 18 | void unlock() { } 19 | }; 20 | #endif 21 | 22 | struct StaticNode; 23 | static StaticNode *gHead, *gTail; 24 | static StaticLock gLock; // lock protecting linked list 25 | 26 | struct StaticLockGuard { 27 | StaticLockGuard(StaticLock* lock) : m_lock(lock) { m_lock->lock(); } 28 | ~StaticLockGuard() { m_lock->unlock(); } 29 | private: 30 | StaticLock* m_lock; 31 | }; 32 | 33 | void StaticNode::init() { 34 | StaticLockGuard locked(&gLock); 35 | if (!gHead) gHead = this; 36 | if (gTail) m_prev = gTail, m_prev->m_next = this; 37 | gTail = this; 38 | } 39 | 40 | void StaticGlobals::initialize() { 41 | StaticLockGuard locked(&gLock); 42 | for (StaticNode *node = gHead; node; node = node->m_next) 43 | node->construct(); 44 | } 45 | 46 | void StaticGlobals::deinitialize() { 47 | StaticLockGuard locked(&gLock); 48 | for (StaticNode *node = gTail; node; node = node->m_prev) 49 | node->destruct(); 50 | } 51 | 52 | StaticNode* StaticGlobals::find(const char *name) { 53 | StaticLockGuard locked(&gLock); 54 | for (StaticNode *node = gHead; node; node = node->m_next) 55 | if (!strcmp(node->m_name, name)) 56 | return node; 57 | return nullptr; 58 | } 59 | 60 | void StaticGlobals::remove(StaticNode *node) { 61 | StaticLockGuard locked(&gLock); 62 | if (node->m_next) node->m_next->m_prev = node->m_prev; 63 | if (node->m_prev) node->m_prev->m_next = node->m_next; 64 | if (gHead == node) gHead = node->m_next; 65 | if (gTail == node) gTail = node->m_prev; 66 | } 67 | -------------------------------------------------------------------------------- /0xabad1dea.h: -------------------------------------------------------------------------------- 1 | #ifndef _0xABAD1DEA 2 | #define _0xABAD1DEA 3 | 4 | #include 5 | 6 | template 7 | struct StaticTypeCarry { typedef T type; }; 8 | 9 | struct StaticNode { 10 | template 11 | StaticNode(const char *name, StaticTypeCarry) : m_name(name), m_next(nullptr), m_prev(nullptr), m_ctor(ctor_), m_dtor(dtor_) { init(); } 12 | void construct() { m_ctor(this + 1); } 13 | void destruct() { m_dtor(this + 1); } 14 | private: 15 | void init(); 16 | template 17 | static void ctor_(void *obj) { new ((T *)obj) T; } 18 | template 19 | static void dtor_(void *obj) { ((T *)obj)->~T(); } 20 | friend struct StaticGlobals; 21 | const char *m_name; 22 | StaticNode *m_next, *m_prev; 23 | void (*m_ctor)(void *), (*m_dtor)(void *); 24 | }; 25 | 26 | template 27 | struct StaticGlobal { 28 | StaticGlobal(const char *name) : node(name, StaticTypeCarry()) { } 29 | T &operator*() { return (T &)data; } 30 | T *operator->() { return (T *)data; } 31 | const T &operator*() const { return (const T&)data; } 32 | const T *operator->() const { return (const T*)data; } 33 | private: 34 | StaticNode node; 35 | alignas(alignof(T)) unsigned char data[sizeof(T)]; 36 | }; 37 | 38 | struct StaticGlobals { 39 | static void initialize(); 40 | static void deinitialize(); 41 | static StaticNode* find(const char *name); 42 | static void remove(StaticNode *node); 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 0xABAD1DEA 2 | 3 | This little header provides an alternative way to construct and destruct static 4 | global objects. 5 | 6 | # How to use 7 | Add `0xabad1dea.cpp` and `0xabad1dea.h` to your project. 8 | 9 | # Examples 10 | 11 | ## Trivial 12 | ```c++ 13 | struct A { 14 | A() { printf("A::A()\n"); } 15 | ~A() { printf("A::~A()\n"); } 16 | }; 17 | StaticGlobal gMyA0("MyA0"), gMyA1("MyA1"); // uninitialized 18 | 19 | int main() { 20 | StaticGlobals::initialize(); // calls the constructors objects are now initialized 21 | 22 | // typically best to use atexit(StaticGlobals::deinitialize) 23 | StaticGlobals::deinitialize(); // calls the destructors 24 | } 25 | ``` 26 | 27 | ## Calling individuals 28 | ```c++ 29 | int main() { 30 | StaticNode *node = StaticGlobals::find("MyA1"); 31 | if (node) node->construct(); // manually initialize MyA1 32 | // ... 33 | if (node) node->destruct(); // manually destruct MyA1 34 | } 35 | ``` 36 | 37 | ## Removing individuals 38 | ```c++ 39 | int main() { 40 | StaticNode *node = StaticGlobals::find("MyA1"); 41 | if (node) StaticGlobals::remove(node); // remove MyA1 from initialization and destruction 42 | } 43 | ``` 44 | 45 | ## Real world example 46 | Have a memory allocator which needs to be allocated before all other static globals, 47 | you could lazily construct it with a singleton pattern or you could use something like: 48 | ```c++ 49 | StaticGlobal gAllocator("Allocator"); 50 | /// ... 51 | int main() { 52 | atexit(StaticGlobals::deinitialize); // be sure to call destructors of statics at exit 53 | StaticNode *node = StaticGlobals::find("Allocator"); 54 | if (node) { 55 | node->initialize(); // call the constructor 56 | StaticGlobals::remove(node); // remove from the statics list 57 | } 58 | StaticGlobals::initialize(); // initialize all other statics 59 | 60 | // ... typical code 61 | 62 | if (node) 63 | node->deinitialize(); // deinitialize the allocator 64 | } 65 | ``` 66 | 67 | Of course the other statics may depend on the allocator to exist (e.g free memory) 68 | so we cannot deinitialize in main, what we could do is something like this which 69 | uses atexit to register a lambda function which destroys the statics before the 70 | other atexit handler for all other static global objects runs. 71 | ```c++ 72 | StaticNode *node; 73 | int main() { 74 | node = StaticGlobals::find("Allocator"); 75 | if (node) { 76 | node->initialize(); // call the constructor 77 | atexit([](){ node->deinitialize(); }); // this atexit will be called first 78 | StaticGlobals::remove(node); // remove from the statics list 79 | } 80 | StaticGlobals::initialize(); // initialize all other statics 81 | atexit(StaticGlobals::deinitialize); // be sure to call destructors of statics at exit 82 | 83 | // normal code 84 | } 85 | ``` 86 | 87 | # How it works 88 | Using an intrusive linked list to thread a doubly linked list of each global 89 | using static storage and then iterating that linked list to actually call 90 | placement new on uninitialized storage to construct the static objects. 91 | The same is done for destruction except the tail end of the linked list is 92 | used so things get destroyed in reverse order as they were constructed. 93 | 94 | The intrusive use of the node makes it convienent to do type-erasure, avoid heap 95 | allocations and most importantly get the associated data backing the actual 96 | object. Take a look at the definition of `StaticGlobal`. It uses a technique to 97 | carry over the template type into node's constructor which the node then uses 98 | to get the address of two wrapper functions for constructing and destructing 99 | the object. The layout of `StaticGlobal` is such that immediately after the 100 | node object there is the data we'll be using to construct the object. So 101 | by going one past a `StaticNode` pointer we're effectively in the data for that 102 | node. 103 | 104 | # Thread safety 105 | Since this system does depend on existing C++ static constructors to function 106 | and C++ permits an implementation to thread the initialization of static objects 107 | a global lock is used on the linked list. The lock is implemented in terms of 108 | `pthread_mutex_t` but can be replaced with a more suitable mutex or locking 109 | primitive of your choosing (it's only ~8 lines or so in the source file.) 110 | 111 | # License 112 | ``` 113 | This is free and unencumbered software released into the public domain. 114 | 115 | Anyone is free to copy, modify, publish, use, compile, sell, or 116 | distribute this software, either in source code form or as a compiled 117 | binary, for any purpose, commercial or non-commercial, and by any 118 | means. 119 | 120 | In jurisdictions that recognize copyright laws, the author or authors 121 | of this software dedicate any and all copyright interest in the 122 | software to the public domain. We make this dedication for the benefit 123 | of the public at large and to the detriment of our heirs and 124 | successors. We intend this dedication to be an overt act of 125 | relinquishment in perpetuity of all present and future rights to this 126 | software under copyright law. 127 | 128 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 129 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 130 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 131 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 132 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 133 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 134 | OTHER DEALINGS IN THE SOFTWARE. 135 | 136 | For more information, please refer to 137 | ``` 138 | --------------------------------------------------------------------------------