The memory allocator supports runtime dynamic size memory allocation

This commit is contained in:
mutouyun 2025-01-26 10:38:07 +08:00 committed by 木头云
parent ef9d3b3642
commit 8b384ba5f2
3 changed files with 87 additions and 19 deletions

View File

@ -53,6 +53,11 @@ void *destroy(T *p) noexcept {
return p;
}
template <>
inline void *destroy<void>(void *p) noexcept {
return p;
}
template <typename T, std::size_t N>
void *destroy(T (*p)[N]) noexcept {
if (p == nullptr) return nullptr;

View File

@ -9,6 +9,7 @@
#include <algorithm>
#include <type_traits>
#include <limits>
#include <tuple>
#include "libipc/imp/aligned.h"
#include "libipc/imp/uninitialized.h"
@ -29,13 +30,13 @@ public:
};
#if defined(LIBIPC_CPP_17)
using get_block_collector_t = block_collector *(*)() noexcept;
using recycle_t = void (*)(void *p, void *o, std::size_t bytes, std::size_t alignment) noexcept;
#else
using get_block_collector_t = block_collector *(*)();
using recycle_t = void (*)(void *p, void *o, std::size_t bytes, std::size_t alignment);
#endif
static constexpr std::size_t regular_head_size
= round_up(sizeof(get_block_collector_t), alignof(std::max_align_t));
= round_up(sizeof(recycle_t), alignof(std::max_align_t));
/// \brief Select the incremental level based on the size.
constexpr inline std::size_t regular_level(std::size_t s) noexcept {
@ -64,6 +65,11 @@ constexpr inline std::size_t regular_sizeof() noexcept {
return regular_sizeof_impl(regular_head_size + sizeof(T));
}
template <>
constexpr inline std::size_t regular_sizeof<void>() noexcept {
return (std::numeric_limits<std::size_t>::max)();
}
/// \brief Use block pools to handle memory less than 64K.
template <std::size_t BlockSize, std::size_t BlockPoolExpansion>
class block_resource_base : public block_pool<BlockSize, BlockPoolExpansion> {
@ -107,20 +113,21 @@ public:
return &instance;
}
template <typename T>
void *allocate(std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
void *p = base_t::allocate(bytes, alignment);
*static_cast<get_block_collector_t *>(p) = get;
*static_cast<recycle_t *>(p)
= [](void *p, void *o, std::size_t bytes, std::size_t alignment) noexcept {
std::ignore = destroy(static_cast<T *>(o));
get()->recycle(p, bytes, alignment);
};
return static_cast<byte *>(p) + regular_head_size;
}
void deallocate(void *p, std::size_t bytes, std::size_t alignment = alignof(std::max_align_t)) noexcept {
p = static_cast<byte *>(p) - regular_head_size;
auto g = *static_cast<get_block_collector_t *>(p);
if (g == get) {
base_t::deallocate(p, bytes, alignment);
return;
}
g()->recycle(p, bytes, alignment);
void *b = static_cast<byte *>(p) - regular_head_size;
auto *r = static_cast<recycle_t *>(b);
(*r)(b, p, bytes, alignment);
}
};
@ -141,13 +148,59 @@ auto *get_regular_resource() noexcept {
return dynamic_cast<block_poll_resource_t *>(block_poll_resource_t::get());
}
namespace detail_new {
template <typename T>
struct do_allocate {
template <typename R, typename... A>
static T *apply(R *res, A &&... args) noexcept {
LIBIPC_TRY {
return construct<T>(res->template allocate<T>(sizeof(T), alignof(T)), std::forward<A>(args)...);
} LIBIPC_CATCH(...) {
return nullptr;
}
}
};
template <>
struct do_allocate<void> {
template <typename R>
static void *apply(R *res, std::size_t bytes) noexcept {
if (bytes == 0) return nullptr;
return res->template allocate<void>(bytes);
}
};
template <typename T>
struct do_deallocate {
template <typename R>
static void apply(R *res, T *p) noexcept {
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
res->deallocate(p, sizeof(T), alignof(T));
#else
// `alignof` of vs2015 requires that type must be able to be instantiated.
res->deallocate(p, sizeof(T));
#endif
}
};
template <>
struct do_deallocate<void> {
template <typename R>
static void apply(R *res, void *p) noexcept {
res->deallocate(p, 0);
}
};
} // namespace detail_new
/// \brief Creates an object based on the specified type and parameters with block pool resource.
/// \note This function is thread-safe.
template <typename T, typename... A>
T *$new(A &&... args) noexcept {
auto *res = get_regular_resource<T>();
if (res == nullptr) return nullptr;
return construct<T>(res->allocate(sizeof(T), alignof(T)), std::forward<A>(args)...);
return detail_new::do_allocate<T>::apply(res, std::forward<A>(args)...);
}
/// \brief Destroys object previously allocated by the `$new` and releases obtained memory area.
@ -156,15 +209,9 @@ T *$new(A &&... args) noexcept {
template <typename T>
void $delete(T *p) noexcept {
if (p == nullptr) return;
destroy(p);
auto *res = get_regular_resource<T>();
if (res == nullptr) return;
#if (LIBIPC_CC_MSVC > LIBIPC_CC_MSVC_2015)
res->deallocate(p, sizeof(T), alignof(T));
#else
// `alignof` of vs2015 requires that type must be able to be instantiated.
res->deallocate(p, sizeof(T));
#endif
detail_new::do_deallocate<T>::apply(res, p);
}
/// \brief The destruction policy used by std::unique_ptr.

View File

@ -139,6 +139,22 @@ TEST(new, delete_null) {
SUCCEED();
}
TEST(new, malloc) {
void *p = ipc::mem::$new<void>(0);
ASSERT_EQ(p, nullptr);
ipc::mem::$delete(p);
p = ipc::mem::$new<void>(1024);
ASSERT_NE(p, nullptr);
ipc::mem::$delete(p);
p = ipc::mem::$new<Derived>(-1);
ASSERT_NE(p, nullptr);
ASSERT_EQ(((Derived *)p)->get(), -1);
ASSERT_EQ(construct_count__, -1);
ipc::mem::$delete(p);
ASSERT_EQ(construct_count__, 0);
}
TEST(new, multi_thread) {
std::array<std::thread, 16> threads;
for (auto &t : threads) {