Placement New & 메모리 풀 구현
placement new는 이미 할당된 메모리 주소에 객체를 생성하는 C++ 기능입니다. 메모리 풀, 임베디드 시스템, 고성능 서버에서 힙 할당 오버헤드를 제거하는 핵심 도구입니다.
Placement New 기본
섹션 제목: “Placement New 기본”#include <new> // placement new 헤더
// 1. 메모리 확보 (어디서든 가능)alignas(MyClass) char buffer[sizeof(MyClass)];
// 2. 그 메모리 위에 객체 생성MyClass* obj = new (buffer) MyClass(arg1, arg2);
// 3. 명시적 소멸자 호출 (delete 사용 금지!)obj->~MyClass();
// 4. 메모리는 별도로 해제 (buffer는 스택이므로 자동 해제)placement new로 생성한 객체는 delete를 호출하면 안 됩니다. delete는 소멸자 호출 + 메모리 해제를 함께 하기 때문에 이미 관리되는 메모리를 해제하려 해 undefined behavior가 됩니다.
정렬(Alignment) 요구사항
섹션 제목: “정렬(Alignment) 요구사항”struct alignas(16) SIMD_Vector { float x, y, z, w;};
// 잘못된 예 — 정렬 보장 없음char raw[sizeof(SIMD_Vector)];auto* v = new (raw) SIMD_Vector(); // 16바이트 정렬 보장 안 됨 → UB
// 올바른 예 1 — alignas 사용alignas(SIMD_Vector) char aligned[sizeof(SIMD_Vector)];auto* v = new (aligned) SIMD_Vector();
// 올바른 예 2 — std::aligned_storage (C++11, deprecated in C++23)std::aligned_storage_t<sizeof(SIMD_Vector), alignof(SIMD_Vector)> storage;auto* v = new (&storage) SIMD_Vector();
// 올바른 예 3 — std::aligned_alloc (C++17)void* mem = std::aligned_alloc(alignof(SIMD_Vector), sizeof(SIMD_Vector));auto* v = new (mem) SIMD_Vector();v->~SIMD_Vector();std::free(mem);Fixed-Size 메모리 풀
섹션 제목: “Fixed-Size 메모리 풀”동일 크기의 객체를 대량 생성·소멸할 때 가장 효과적입니다.
#include <cstddef>#include <cassert>#include <new>
template <typename T, std::size_t Capacity>class FixedPool {public: FixedPool() { // 프리 리스트 초기화 for (std::size_t i = 0; i < Capacity - 1; ++i) blocks_[i].next = &blocks_[i + 1]; blocks_[Capacity - 1].next = nullptr; freeHead_ = &blocks_[0]; }
template <typename... Args> T* allocate(Args&&... args) { if (!freeHead_) return nullptr; // 풀 소진
Block* block = freeHead_; freeHead_ = freeHead_->next;
// 블록 메모리에 T 객체 생성 return new (&block->storage) T(std::forward<Args>(args)...); }
void deallocate(T* ptr) { if (!ptr) return;
ptr->~T(); // 명시적 소멸자 호출
// 블록을 프리 리스트로 반환 Block* block = reinterpret_cast<Block*>(ptr); block->next = freeHead_; freeHead_ = block; }
std::size_t available() const { std::size_t count = 0; for (Block* b = freeHead_; b; b = b->next) ++count; return count; }
private: union Block { alignas(T) char storage[sizeof(T)]; Block* next; };
Block blocks_[Capacity]; Block* freeHead_ = nullptr;};
// 사용 예FixedPool<Bullet, 256> bulletPool;
Bullet* b = bulletPool.allocate(position, velocity);// ... 게임 로직 ...bulletPool.deallocate(b);장점:
- 할당/해제: O(1)
- 메모리 단편화 없음
- 캐시 친화적 (연속 메모리)
Stack Allocator (선형 할당기)
섹션 제목: “Stack Allocator (선형 할당기)”레벨 로딩처럼 일괄 해제가 가능한 경우에 적합합니다.
#include <cstddef>#include <cstdint>#include <cassert>
class StackAllocator {public: explicit StackAllocator(std::size_t size) : buffer_(new std::byte[size]) , capacity_(size) , offset_(0) {}
~StackAllocator() { delete[] buffer_; }
// 정렬을 고려한 할당 void* allocate(std::size_t size, std::size_t alignment = alignof(std::max_align_t)) { std::size_t aligned = align_up(offset_, alignment); assert(aligned + size <= capacity_);
void* ptr = buffer_ + aligned; offset_ = aligned + size; return ptr; }
// 마커 기반 해제 using Marker = std::size_t;
Marker getMarker() const { return offset_; }
void freeToMarker(Marker marker) { assert(marker <= offset_); offset_ = marker; // 소멸자는 사용자가 직접 호출해야 함! }
// 전체 리셋 void reset() { offset_ = 0; }
std::size_t used() const { return offset_; } std::size_t remaining() const { return capacity_ - offset_; }
private: std::size_t align_up(std::size_t val, std::size_t align) { return (val + align - 1) & ~(align - 1); }
std::byte* buffer_; std::size_t capacity_; std::size_t offset_;};
// 사용 예StackAllocator frameAlloc(1024 * 1024); // 1MB 프레임 할당기
void renderFrame() { auto marker = frameAlloc.getMarker();
// 프레임 내 임시 데이터 할당 void* mem = frameAlloc.allocate(sizeof(TempData), alignof(TempData)); TempData* tmp = new (mem) TempData();
// ... 렌더링 ...
tmp->~TempData(); // 소멸자 명시 호출 frameAlloc.freeToMarker(marker); // 한 번에 해제}std::allocator와 통합
섹션 제목: “std::allocator와 통합”STL 컨테이너에 커스텀 할당기를 적용합니다.
#include <memory>#include <vector>
template <typename T>class PoolAllocator {public: using value_type = T;
PoolAllocator() = default;
template <typename U> PoolAllocator(const PoolAllocator<U>&) noexcept {}
T* allocate(std::size_t n) { // 풀에서 n * sizeof(T) 바이트 할당 return static_cast<T*>(globalPool.allocate(n * sizeof(T), alignof(T))); }
void deallocate(T* ptr, std::size_t n) noexcept { globalPool.deallocate(ptr, n * sizeof(T)); }
bool operator==(const PoolAllocator&) const noexcept { return true; } bool operator!=(const PoolAllocator&) const noexcept { return false; }
private: // 실제 구현에서는 스레드 로컬 또는 전역 풀 참조 static StackAllocator globalPool;};
// 사용std::vector<int, PoolAllocator<int>> poolVec;poolVec.push_back(42);주요 함정
섹션 제목: “주요 함정”// 함정 1: delete 사용T* obj = pool.allocate();delete obj; // 절대 안 됨! 풀 메모리를 시스템에 반환하려 함
// 올바름obj->~T();pool.deallocate(obj);
// 함정 2: 소멸자 미호출pool.deallocate(obj); // 소멸자 없이 반환 → 리소스 누수
// 올바름obj->~T(); // 먼저 소멸자pool.deallocate(obj); // 그 다음 반환
// 함정 3: 정렬 무시char buf[sizeof(T)]; // 정렬 보장 없음new (buf) T(); // UB 가능
// 올바름alignas(T) char buf[sizeof(T)];new (buf) T();핵심 요약
섹션 제목: “핵심 요약”new (ptr) T(args)→ 지정 주소에 생성- 소멸은
ptr->~T()명시 호출 (delete 금지) - 정렬은
alignas(T)또는std::aligned_alloc으로 보장 - Fixed-size pool: O(1) 할당/해제, 게임 오브젝트에 적합
- Stack allocator: 프레임/레벨 단위 일괄 해제에 적합
- STL 통합:
Allocator콘셉트 구현으로vector,map등에 적용