mirror of
https://github.com/esphome/esphome.git
synced 2026-10-02 17:30:22 +00:00
[core] Add RAMAllocator::make_unique for objects whose allocation may fail (#19245)
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@@ -13,9 +14,11 @@
|
|||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <new>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
@@ -2123,6 +2126,10 @@ void delay_microseconds_safe(uint32_t us);
|
|||||||
/// @name Memory management
|
/// @name Memory management
|
||||||
///@{
|
///@{
|
||||||
|
|
||||||
|
template<typename T> struct RAMDeleter;
|
||||||
|
/// unique_ptr over RAMAllocator storage
|
||||||
|
template<typename T> using RAMUniquePtr = std::unique_ptr<T, RAMDeleter<T>>;
|
||||||
|
|
||||||
/** An STL allocator that uses SPI or internal RAM.
|
/** An STL allocator that uses SPI or internal RAM.
|
||||||
* Returns `nullptr` in case no memory is available.
|
* Returns `nullptr` in case no memory is available.
|
||||||
*
|
*
|
||||||
@@ -2193,6 +2200,26 @@ template<class T> class RAMAllocator {
|
|||||||
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
free(p); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Value initialize one T; empty on exhaustion. new (std::nothrow) aborts on ESP-IDF instead.
|
||||||
|
/// Default flags prefer PSRAM; pass PREFER_INTERNAL to keep an object where plain new put it.
|
||||||
|
template<typename... Args> RAMUniquePtr<T> make_unique(Args &&...args) {
|
||||||
|
static_assert(alignof(T) <= alignof(std::max_align_t), "malloc storage cannot hold an over aligned type");
|
||||||
|
T *p = this->allocate(1);
|
||||||
|
if (p == nullptr)
|
||||||
|
return {};
|
||||||
|
// ::new so a class scoped operator new cannot hide the global placement form
|
||||||
|
return RAMUniquePtr<T>(::new (p) T(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// n elements left uninitialized, as std::make_unique_for_overwrite does; empty on exhaustion, overflow, and n == 0
|
||||||
|
RAMUniquePtr<T[]> make_unique_array_for_overwrite(size_t n) {
|
||||||
|
static_assert(std::is_trivially_default_constructible_v<T>, "elements are left unconstructed");
|
||||||
|
static_assert(alignof(T) <= alignof(std::max_align_t), "malloc storage cannot hold an over aligned type");
|
||||||
|
if (n == 0 || n > SIZE_MAX / sizeof(T))
|
||||||
|
return {};
|
||||||
|
return RAMUniquePtr<T[]>(this->allocate(n));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the total heap space available via this allocator
|
* Return the total heap space available via this allocator
|
||||||
*/
|
*/
|
||||||
@@ -2255,6 +2282,19 @@ template<class T> class RAMAllocator {
|
|||||||
|
|
||||||
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
template<class T> using ExternalRAMAllocator = RAMAllocator<T>;
|
||||||
|
|
||||||
|
/// Destroys and frees RAMAllocator storage. Not convertible: free() needs the address malloc returned
|
||||||
|
template<typename T> struct RAMDeleter {
|
||||||
|
void operator()(T *p) const {
|
||||||
|
p->~T();
|
||||||
|
RAMAllocator<T>().deallocate(p, 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// Array form: elements must be trivial, the count is not stored so only the storage is freed
|
||||||
|
template<typename T> struct RAMDeleter<T[]> {
|
||||||
|
static_assert(std::is_trivially_destructible_v<T>, "RAMUniquePtr<T[]> is for trivially destructible elements");
|
||||||
|
void operator()(T *p) const { RAMAllocator<T>().deallocate(p, 1); }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions to constrain the range of arithmetic values.
|
* Functions to constrain the range of arithmetic values.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -367,4 +367,56 @@ TEST(FixedVectorTryInit, ReportsExhaustionAndStaysEmpty) {
|
|||||||
EXPECT_EQ(v.size(), 1u);
|
EXPECT_EQ(v.size(), 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- RAMAllocator::make_unique() ---
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Probe {
|
||||||
|
static inline int live = 0;
|
||||||
|
int a;
|
||||||
|
int b;
|
||||||
|
Probe(int a, int b) : a(a), b(b) { live++; }
|
||||||
|
~Probe() { live--; }
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static_assert(sizeof(RAMUniquePtr<Probe>) == sizeof(Probe *), "the deleter must not add storage");
|
||||||
|
|
||||||
|
TEST(RAMAllocatorMakeUnique, ForwardsArgsAndDestroysOnce) {
|
||||||
|
auto p = RAMAllocator<Probe>().make_unique(3, 4);
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
EXPECT_EQ(p->a, 3);
|
||||||
|
EXPECT_EQ(p->b, 4);
|
||||||
|
EXPECT_EQ(Probe::live, 1);
|
||||||
|
p.reset();
|
||||||
|
EXPECT_EQ(Probe::live, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RAMAllocatorMakeUnique, ValueInitializesLikeMakeUnique) {
|
||||||
|
struct Plain {
|
||||||
|
uint32_t words[8];
|
||||||
|
};
|
||||||
|
// Dirty a block of the same size first so a recycled allocation is not zero by chance
|
||||||
|
auto dirty = RAMAllocator<uint8_t>().make_unique_array_for_overwrite(sizeof(Plain));
|
||||||
|
std::memset(dirty.get(), 0xFF, sizeof(Plain));
|
||||||
|
dirty.reset();
|
||||||
|
auto p = RAMAllocator<Plain>().make_unique();
|
||||||
|
ASSERT_NE(p, nullptr);
|
||||||
|
// Under ASan fresh blocks are filled with 0xbe, so this holds even when the dirtied block is not reused
|
||||||
|
EXPECT_TRUE(std::all_of(std::begin(p->words), std::end(p->words), [](uint32_t w) { return w == 0; }));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RAMAllocatorMakeUnique, ArrayFormRejectsOverflowAndZero) {
|
||||||
|
EXPECT_EQ(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(SIZE_MAX / sizeof(uint32_t) + 1), nullptr);
|
||||||
|
EXPECT_EQ(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(0), nullptr);
|
||||||
|
EXPECT_NE(RAMAllocator<uint32_t>().make_unique_array_for_overwrite(1), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(RAMAllocatorMakeUnique, ArrayFormAllocatesElements) {
|
||||||
|
RAMUniquePtr<uint8_t[]> buf = RAMAllocator<uint8_t>().make_unique_array_for_overwrite(256);
|
||||||
|
ASSERT_NE(buf, nullptr);
|
||||||
|
std::memset(buf.get(), 0xA5, 256);
|
||||||
|
EXPECT_EQ(buf[0], 0xA5);
|
||||||
|
EXPECT_EQ(buf[255], 0xA5);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace esphome::core::testing
|
} // namespace esphome::core::testing
|
||||||
|
|||||||
Reference in New Issue
Block a user