diff --git a/intern/guardedalloc/MEM_guardedalloc.h b/intern/guardedalloc/MEM_guardedalloc.h index 80cb8d27a4f..a8eca7539a9 100644 --- a/intern/guardedalloc/MEM_guardedalloc.h +++ b/intern/guardedalloc/MEM_guardedalloc.h @@ -9,7 +9,7 @@ * * \page MEMPage Guarded memory(de)allocation * - * \section aboutmem c-style guarded memory allocation + * \section aboutmem c-style & C++-style guarded memory allocation * * \subsection memabout About the MEM module * @@ -18,10 +18,10 @@ * linked list, so they remain reachable at all times. There is no * back-up in case the linked-list related data is lost. * - * \subsection memissues Known issues with MEM - * - * There are currently no known issues with MEM. Note that there is a - * second intern/ module with MEM_ prefix, for use in c++. + * It also provides C++ template versions of [cm]alloc and related API, + * which prodives improved type safety, ensures that the allocated types + * are trivial, and reduces the casting verbosity by directly returning + * a pointer of the expected type. * * \subsection memdependencies Dependencies * - `stdlib` @@ -44,6 +44,19 @@ extern "C" { #endif +/* -------------------------------------------------------------------- */ +/** + * \name Untyped Allocation API. + * + * Defines the 'C-style' part of the API, where memory management is fully untyped (i.e. done with + * void pointers and explicit size values). + * + * This API should usually not be used anymore in C++ code, unless some form of raw memory + * mamangement is necessary (e.g. for allocation of various ID types based on their + * #IDTypeInfo::struct_size data). + * + * \{ */ + /** * Returns the length of the allocated memory segment pointed at * by vmemh. If the pointer was not previously allocated by this @@ -156,6 +169,36 @@ extern void *(*MEM_calloc_arrayN_aligned)( const char *str) /* ATTR_MALLOC */ ATTR_WARN_UNUSED_RESULT ATTR_ALLOC_SIZE(1, 2) ATTR_NONNULL(4); +#ifdef __cplusplus +/* Implicitely uses the templated, type-safe version of #MEM_freeN, unless `v` is `void *`. */ +# define MEM_SAFE_FREE(v) \ + do { \ + if (v) { \ + MEM_freeN(v); \ + (v) = nullptr; \ + } \ + } while (0) +#else +# define MEM_SAFE_FREE(v) \ + do { \ + void **_v = (void **)&(v); \ + if (*_v) { \ + MEM_freeN(*_v); \ + *_v = NULL; \ + } \ + } while (0) +#endif + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** + * \name Various Helpers. + * + * These functions allow to control the behavior of the guarded allocator, and to retrieve (debug) + * information about allocated memory. + */ + /** * Print a list of the names and sizes of all allocated memory * blocks. as a python dict for easy investigation. @@ -197,25 +240,6 @@ extern void (*MEM_reset_peak_memory)(void); /** Get the peak memory usage in bytes, including `mmap` allocations. */ extern size_t (*MEM_get_peak_memory)(void) ATTR_WARN_UNUSED_RESULT; -#ifdef __cplusplus -# define MEM_SAFE_FREE(v) \ - do { \ - if (v) { \ - MEM_freeN(v); \ - (v) = nullptr; \ - } \ - } while (0) -#else -# define MEM_SAFE_FREE(v) \ - do { \ - void **_v = (void **)&(v); \ - if (*_v) { \ - MEM_freeN(*_v); \ - *_v = NULL; \ - } \ - } while (0) -#endif - /** Overhead for lockfree allocator (use to avoid slop-space). */ #define MEM_SIZE_OVERHEAD sizeof(size_t) #define MEM_SIZE_OPTIMAL(size) ((size)-MEM_SIZE_OVERHEAD) @@ -274,6 +298,8 @@ void MEM_use_lockfree_allocator(void); */ void MEM_use_guarded_allocator(void); +/** \} */ + #ifdef __cplusplus } #endif /* __cplusplus */ @@ -297,6 +323,23 @@ void MEM_use_guarded_allocator(void); (__STDCPP_DEFAULT_NEW_ALIGNMENT__ < alignof(void *) ? __STDCPP_DEFAULT_NEW_ALIGNMENT__ : \ alignof(void *)) +/* -------------------------------------------------------------------- */ +/** + * \name Type-aware allocation & construction API. + * + * Defines some `new`/`delete`-like helpers, which allocate/free memory using `MEM_guardedalloc`, + * and construct/destruct the objects. + * + * When possible, it is prefferred to use these, even on trivial types, as it makes potential + * future changes to these types less disruptive, and is overall closer to standard C++ data + * creation and destruction. + * + * However, if the type is trivial, `MEM_[cm]allocN` and related functions can be used to + * allocate an object that will be managed by external historic code still using C-style + * allocation/duplication/freeing. + * + * \{ */ + /** * Allocate new memory for an object of type #T, and construct it. * #MEM_delete must be used to delete the object. Calling #MEM_freeN on it is illegal. @@ -354,155 +397,7 @@ template inline void MEM_delete(const T *ptr) } \ } while (0) -/** - * Allocate zero-initialized memory for an object of type #T. The constructor of #T is not called, - * therefore this must only be used with trivial types (like all C types). - * - * When allocating an enforced specific amount of bytes, the C version of this function should be - * used instead. While this should be avoided in C++ code, it is still required in some cases, e.g. - * for ID allocation based on #IDTypeInfo::struct_size. - * - * #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is - * illegal. - */ -template inline T *MEM_callocN(const char *allocation_name) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); -# endif - return static_cast(MEM_calloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name)); -} - -/** - * Type-safe version of #MEM_calloc_arrayN/#MEM_calloc_array_alignedN. - */ -template inline T *MEM_calloc_arrayN(const size_t length, const char *allocation_name) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); -# endif - return static_cast( - MEM_calloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); -} - -/** - * Allocate uninitialized memory for an object of type #T. The constructor of #T is not called, - * therefore this must only be used with trivial types (like all C types). - * - * When allocating an enforced specific amount of bytes, the C version of this function should be - * used instead. While this should be avoided in C++ code, it is still required in some cases, e.g. - * for ID allocation based on #IDTypeInfo::struct_size. - * - * #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is - * illegal. - */ -template inline T *MEM_mallocN(const char *allocation_name) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); -# endif - return static_cast(MEM_malloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name)); -} - -/** - * Type-safe version of #MEM_malloc_arrayN/#MEM_malloc_array_alignedN. - */ -template inline T *MEM_malloc_arrayN(const size_t length, const char *allocation_name) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); -# endif - return static_cast( - MEM_malloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); -} - -/** - * Allocate memory for an object of type #T and memory-copy `other` into it. - * Only applicable for trivial types. - * - * This function works around the problem of copy-constructing DNA structs which contains - * deprecated fields: some compilers will generate access deprecated field warnings in implicitly - * defined copy constructors. - * - * This is a better alternative to the C-style implementation of #MEM_dupallocN, unless the source - * is an array or of a non-fully-defined type. - */ -template inline T *MEM_dupallocN(const char *allocation_name, const T &other) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_constructible_v, - "For non-trivial types, MEM_new must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); -# endif - T *new_object = static_cast(MEM_mallocN_aligned(sizeof(T), alignof(T), allocation_name)); - if (new_object) { - memcpy(new_object, &other, sizeof(T)); - } - return new_object; -} - -template inline void MEM_freeN(T *ptr) -{ -# ifdef _MSC_VER - /* MSVC considers C-style types using the DNA_DEFINE_CXX_METHODS as non-trivial (more - * specifically, non-trivially copyable, likely because the default copy constructors are - * deleted). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do not. - * - * So for now, use a more restricted check on MSVC, should still catch most of actual invalid - * cases. */ - static_assert(std::is_trivially_destructible_v, - "For non-trivial types, MEM_delete must be used."); -# else - static_assert(std::is_trivial_v, "For non-trivial types, MEM_delete must be used."); -# endif - mem_guarded::internal::mem_freeN_ex(const_cast(static_cast(ptr)), - mem_guarded::internal::AllocationType::ALLOC_FREE); -} - -/** Allocation functions (for C++ only). */ +/** Define overloaded new/delete operators for C++ types. */ # define MEM_CXX_CLASS_ALLOC_FUNCS(_id) \ public: \ void *operator new(size_t num_bytes) \ @@ -559,6 +454,142 @@ template inline void MEM_freeN(T *ptr) */ \ void operator delete(void * /*ptr_to_free*/, void * /*ptr*/) {} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** + * \name Type-aware allocation API. + * + * Templated, type-safe versions of C-style allocation & freeing API. + * + * These functions only allocate or free memory, without any calls to constructors or destructors. + * + * \note MSVC considers C-style types using the #DNA_DEFINE_CXX_METHODS as non-trivial (more + * specifically, non-trivially copyable, likely because the default copy constructors are + * deleted by this macro). GCC and clang (both on linux, OSX, and clang-cl on Windows on Arm) do + * not. So for now, `MEM_[cm]allocN` and related templates use slightly more relaxed checks on + * MSVC. These should still catch most of the real-life invalid cases. + * + * \{ */ + +/** + * Allocate zero-initialized memory for an object of type #T. The constructor of #T is not called, + * therefore this must only be used with trivial types (like all C types). + * + * When allocating an enforced specific amount of bytes, the C version of this function should be + * used instead. While this should be avoided in C++ code, it is still required in some cases, e.g. + * for ID allocation based on #IDTypeInfo::struct_size. + * + * #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is + * illegal. + */ +template inline T *MEM_callocN(const char *allocation_name) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast(MEM_calloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name)); +} + +/** + * Type-safe version of #MEM_calloc_arrayN/#MEM_calloc_array_alignedN. + * + * It has the same restrictions and limitations as the type-safe version of #MEM_callocN. + */ +template inline T *MEM_calloc_arrayN(const size_t length, const char *allocation_name) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast( + MEM_calloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); +} + +/** + * Allocate uninitialized memory for an object of type #T. The constructor of #T is not called, + * therefore this must only be used with trivial types (like all C types). + * + * When allocating an enforced specific amount of bytes, the C version of this function should be + * used instead. While this should be avoided in C++ code, it is still required in some cases, e.g. + * for ID allocation based on #IDTypeInfo::struct_size. + * + * #MEM_freeN must be used to free a pointer returned by this call. Calling #MEM_delete on it is + * illegal. + */ +template inline T *MEM_mallocN(const char *allocation_name) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast(MEM_malloc_arrayN_aligned(1, sizeof(T), alignof(T), allocation_name)); +} + +/** + * Type-safe version of #MEM_malloc_arrayN/#MEM_malloc_array_alignedN. + * + * It has the same restrictions and limitations as the type-safe version of #MEM_mallocN. + */ +template inline T *MEM_malloc_arrayN(const size_t length, const char *allocation_name) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + return static_cast( + MEM_malloc_arrayN_aligned(length, sizeof(T), alignof(T), allocation_name)); +} + +/** + * Allocate memory for an object of type #T and memory-copy `other` into it. + * Only applicable for trivial types. + * + * This function works around the problem of copy-constructing DNA structs which contains + * deprecated fields: some compilers will generate access deprecated field warnings in implicitly + * defined copy constructors. + * + * This is a better alternative to the C-style implementation of #MEM_dupallocN, unless the source + * is an array or of a non-fully-defined type. + */ +template inline T *MEM_dupallocN(const char *allocation_name, const T &other) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_constructible_v, + "For non-trivial types, MEM_new must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_new must be used."); +# endif + T *new_object = static_cast(MEM_mallocN_aligned(sizeof(T), alignof(T), allocation_name)); + if (new_object) { + memcpy(new_object, &other, sizeof(T)); + } + return new_object; +} + +template inline void MEM_freeN(T *ptr) +{ +# ifdef _MSC_VER + static_assert(std::is_trivially_destructible_v, + "For non-trivial types, MEM_delete must be used."); +# else + static_assert(std::is_trivial_v, "For non-trivial types, MEM_delete must be used."); +# endif + mem_guarded::internal::mem_freeN_ex(const_cast(static_cast(ptr)), + mem_guarded::internal::AllocationType::ALLOC_FREE); +} + +/** \} */ + /** * Construct a T that will only be destructed after leak detection is run. *