1 /*********************************************************************************************************************************** 2 Memory Context Manager 3 4 Memory is allocated inside contexts and all allocations (and child memory contexts) are freed when the context is freed. The goal 5 is to make memory management both easier and more performant. 6 7 Memory context management is encapsulated in macros so there is rarely any need to call the functions directly. Memory allocations 8 are mostly performed in the constructors of objects and reallocated as needed. 9 10 See the sections on memory context management and memory allocations below for more details. 11 ***********************************************************************************************************************************/ 12 #ifndef COMMON_MEMCONTEXT_H 13 #define COMMON_MEMCONTEXT_H 14 15 #include <stddef.h> 16 17 /*********************************************************************************************************************************** 18 Memory context object 19 ***********************************************************************************************************************************/ 20 typedef struct MemContext MemContext; 21 22 #include "common/error.h" 23 24 /*********************************************************************************************************************************** 25 Define initial number of memory contexts 26 27 No space is reserved for child contexts when a new context is created because most contexts will be leaves. When a child context is 28 requested then space will be reserved for this many child contexts initially. When more space is needed the size will be doubled. 29 ***********************************************************************************************************************************/ 30 #define MEM_CONTEXT_INITIAL_SIZE 4 31 32 /*********************************************************************************************************************************** 33 Define initial number of memory allocations 34 35 Space is reserved for this many allocations when a context is created. When more space is needed the size will be doubled. 36 ***********************************************************************************************************************************/ 37 #define MEM_CONTEXT_ALLOC_INITIAL_SIZE 4 38 39 /*********************************************************************************************************************************** 40 Memory management functions 41 42 All these functions operate in the current memory context, including memResize() and memFree(). 43 ***********************************************************************************************************************************/ 44 // Allocate memory in the current memory context 45 void *memNew(size_t size); 46 47 // Allocate requested number of pointers and initialize them to NULL 48 void *memNewPtrArray(size_t size); 49 50 // Reallocate to the new size. Original buffer pointer is undefined on return. 51 void *memResize(const void *buffer, size_t size); 52 53 // Free memory allocation 54 void memFree(void *buffer); 55 56 /*********************************************************************************************************************************** 57 Ensure that the prior memory context is restored after the block executes (even on error) 58 59 MEM_CONTEXT_BEGIN(memContext) 60 { 61 <The mem context specified is now the current context> 62 <Prior context can be accessed with the memContextPrior() function> 63 } 64 MEM_CONTEXT_END(); 65 66 <Prior memory context is restored> 67 ***********************************************************************************************************************************/ 68 #define MEM_CONTEXT_BEGIN(memContext) \ 69 do \ 70 { \ 71 /* Switch to the new memory context */ \ 72 memContextSwitch(memContext); 73 74 #define MEM_CONTEXT_END() \ 75 /* Switch back to the prior context */ \ 76 memContextSwitchBack(); \ 77 } \ 78 while (0) 79 80 /*********************************************************************************************************************************** 81 Switch to prior context and ensure that the previous prior memory context is restored after the block executes (even on error) 82 83 MEM_CONTEXT_PRIOR_BEGIN(memContext) 84 { 85 <The mem context specified is now the prior context> 86 } 87 MEM_CONTEXT_PRIOR_END(); 88 89 <Previous prior memory context is restored> 90 ***********************************************************************************************************************************/ 91 #define MEM_CONTEXT_PRIOR_BEGIN() \ 92 MEM_CONTEXT_BEGIN(memContextPrior()) 93 94 #define MEM_CONTEXT_PRIOR_END() \ 95 MEM_CONTEXT_END() 96 97 /*********************************************************************************************************************************** 98 Create a new context and make sure it is freed on error and prior context is restored in all cases 99 100 MEM_CONTEXT_NEW_BEGIN(memContextName) 101 { 102 <The mem context created is now the current context and can be accessed with the MEM_CONTEXT_NEW() macro> 103 104 ObjectType *object = memNew(sizeof(ObjectType)); 105 object->memContext = MEM_CONTEXT_NEW(); 106 107 <Prior context can be accessed with the memContextPrior() function> 108 <On error the newly created context will be freed and the error rethrown> 109 } 110 MEM_CONTEXT_NEW_END(); 111 112 <Prior memory context is restored> 113 114 Note that memory context names are expected to live for the lifetime of the context -- no copy is made. 115 ***********************************************************************************************************************************/ 116 #define MEM_CONTEXT_NEW() \ 117 MEM_CONTEXT_NEW_memContext 118 119 #define MEM_CONTEXT_NEW_BEGIN(memContextName) \ 120 do \ 121 { \ 122 MemContext *MEM_CONTEXT_NEW() = memContextNew(memContextName); \ 123 memContextSwitch(MEM_CONTEXT_NEW()); 124 125 #define MEM_CONTEXT_NEW_END() \ 126 memContextSwitchBack(); \ 127 memContextKeep(); \ 128 } \ 129 while (0) 130 131 /*********************************************************************************************************************************** 132 Create a temporary memory context and make sure it is freed when done (even on error) 133 134 MEM_CONTEXT_TEMP_BEGIN() 135 { 136 <A temp memory context is now the current context> 137 <Temp context can be accessed with the MEM_CONTEXT_TEMP() macro> 138 <Prior context can be accessed with the memContextPrior() function> 139 } 140 MEM_CONTEXT_TEMP_END(); 141 142 <Prior memory context is restored> 143 <Temp memory context is freed> 144 ***********************************************************************************************************************************/ 145 #define MEM_CONTEXT_TEMP() \ 146 MEM_CONTEXT_TEMP_memContext 147 148 #define MEM_CONTEXT_TEMP_BEGIN() \ 149 do \ 150 { \ 151 MemContext *MEM_CONTEXT_TEMP() = memContextNew("temporary"); \ 152 memContextSwitch(MEM_CONTEXT_TEMP()); 153 154 #define MEM_CONTEXT_TEMP_RESET_BEGIN() \ 155 MEM_CONTEXT_TEMP_BEGIN() \ 156 unsigned int MEM_CONTEXT_TEMP_loopTotal = 0; 157 158 #define MEM_CONTEXT_TEMP_RESET(resetTotal) \ 159 do \ 160 { \ 161 MEM_CONTEXT_TEMP_loopTotal++; \ 162 \ 163 if (MEM_CONTEXT_TEMP_loopTotal >= resetTotal) \ 164 { \ 165 memContextSwitchBack(); \ 166 memContextDiscard(); \ 167 MEM_CONTEXT_TEMP() = memContextNew("temporary"); \ 168 memContextSwitch(MEM_CONTEXT_TEMP()); \ 169 MEM_CONTEXT_TEMP_loopTotal = 0; \ 170 } \ 171 } \ 172 while (0) 173 174 #define MEM_CONTEXT_TEMP_END() \ 175 memContextSwitchBack(); \ 176 memContextDiscard(); \ 177 } \ 178 while (0) 179 180 /*********************************************************************************************************************************** 181 Memory context management functions 182 183 memContextSwitch(memContextNew()); 184 185 <Do something with the memory context, e.g. allocation memory with memNew()> 186 <Current memory context can be accessed with memContextCurrent()> 187 <Prior memory context can be accessed with memContextPrior()> 188 189 memContextSwitchBack(); 190 191 <The memory context must now be kept or discarded> 192 memContextKeep()/memContextDiscard(); 193 194 There is no need to implement any error handling. The mem context system will automatically clean up any mem contexts that were 195 created but not marked as keep when an error occurs and reset the current mem context to whatever it was at the beginning of the 196 nearest try block. 197 198 Use the MEM_CONTEXT*() macros when possible rather than reimplement the boilerplate for every memory context block. 199 ***********************************************************************************************************************************/ 200 // Create a new mem context in the current mem context. The new context must be either kept with memContextKeep() or discarded with 201 // memContextDisard() before switching back from the parent context. 202 MemContext *memContextNew(const char *name); 203 204 // Switch to a context making it the current mem context 205 void memContextSwitch(MemContext *this); 206 207 // Switch back to the context that was current before the last switch. If the last function called was memContextNew(), then 208 // memContextKeep()/memContextDiscard() must be called before calling memContextSwitchBack(), otherwise an error will occur in 209 // debug builds and the behavior is undefined in production builds. 210 void memContextSwitchBack(void); 211 212 // Keep a context created by memContextNew() so that it will not be automatically freed if an error occurs. If the context was 213 // switched after the call to memContextNew(), then it must be switched back before calling memContextKeep() or an error will occur 214 // in debug builds and the behavior is undefined in production builds. 215 void memContextKeep(void); 216 217 // Discard a context created by memContextNew(). If the context was switched after the call to memContextNew(), then it must be 218 // switched back before calling memContextDiscard() or an error will occur in debug builds and the behavior is undefined in 219 // production builds. 220 void memContextDiscard(void); 221 222 // Move mem context to a new parent. This is generally used to move objects to a new context once they have been successfully 223 // created. 224 void memContextMove(MemContext *this, MemContext *parentNew); 225 226 // Set a function that will be called when this mem context is freed 227 void memContextCallbackSet(MemContext *this, void (*callbackFunction)(void *), void *); 228 229 // Clear the callback function so it won't be called when the mem context is freed. This is usually done in the object free method 230 // after resources have been freed but before memContextFree() is called. The goal is to prevent the object free method from being 231 // called more than once. 232 void memContextCallbackClear(MemContext *this); 233 234 // Free a memory context 235 void memContextFree(MemContext *this); 236 237 /*********************************************************************************************************************************** 238 Memory context getters 239 ***********************************************************************************************************************************/ 240 // Current memory context 241 MemContext *memContextCurrent(void); 242 243 // Is the mem context currently being freed? 244 bool memContextFreeing(MemContext *this); 245 246 // Prior context, i.e. the context that was current before the last memContextSwitch() 247 MemContext *memContextPrior(void); 248 249 // "top" context. This context is created at initialization and is always present, i.e. it is never freed. The top context is a 250 // good place to put long-lived mem contexts since they won't be automatically freed until the program exits. 251 MemContext *memContextTop(void); 252 253 // Mem context name 254 const char *memContextName(MemContext *this); 255 256 // Get total size of mem context and all children 257 size_t memContextSize(const MemContext *this); 258 259 /*********************************************************************************************************************************** 260 Macros for function logging 261 ***********************************************************************************************************************************/ 262 #define FUNCTION_LOG_MEM_CONTEXT_TYPE \ 263 MemContext * 264 #define FUNCTION_LOG_MEM_CONTEXT_FORMAT(value, buffer, bufferSize) \ 265 objToLog(value, "MemContext", buffer, bufferSize) 266 267 /*********************************************************************************************************************************** 268 Internal functions 269 ***********************************************************************************************************************************/ 270 // Clean up mem contexts after an error. Should only be called from error handling routines. 271 void memContextClean(unsigned int tryDepth); 272 273 #endif 274