C++ Allocator | "메모리 할당자" 커스터마이징 가이드

C++ Allocator | "메모리 할당자" 커스터마이징 가이드

이 글의 핵심

C++ Allocator에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

기본 Allocator

#include <memory>

// 기본 할당자
allocator<int> alloc;

// 메모리 할당
int* ptr = alloc.allocate(10);  // 10개 int 공간

// 객체 생성
alloc.construct(ptr, 42);

// 객체 파괴
alloc.destroy(ptr);

// 메모리 해제
alloc.deallocate(ptr, 10);

컨테이너와 Allocator

// 기본 할당자
vector<int> v1;

// 커스텀 할당자
vector<int, MyAllocator<int>> v2;

// 할당자 전달
MyAllocator<int> alloc;
vector<int, MyAllocator<int>> v3(alloc);

커스텀 Allocator 구현

template<typename T>
class MyAllocator {
public:
    using value_type = T;
    
    MyAllocator() noexcept {}
    
    template<typename U>
    MyAllocator(const MyAllocator<U>&) noexcept {}
    
    T* allocate(size_t n) {
        cout << "할당: " << n << "개" << endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* ptr, size_t n) noexcept {
        cout << "해제: " << n << "개" << endl;
        ::operator delete(ptr);
    }
};

template<typename T, typename U>
bool operator==(const MyAllocator<T>&, const MyAllocator<U>&) {
    return true;
}

template<typename T, typename U>
bool operator!=(const MyAllocator<T>&, const MyAllocator<U>&) {
    return false;
}

int main() {
    vector<int, MyAllocator<int>> v;
    
    v.push_back(1);  // 할당 발생
    v.push_back(2);
    v.push_back(3);
}

실전 예시

예시 1: 메모리 풀 할당자

template<typename T>
class PoolAllocator {
private:
    struct Block {
        T data;
        Block* next;
    };
    
    Block* freeList = nullptr;
    vector<Block*> pools;
    
    static constexpr size_t POOL_SIZE = 1024;
    
    void allocatePool() {
        Block* pool = static_cast<Block*>(::operator new(POOL_SIZE * sizeof(Block)));
        pools.push_back(pool);
        
        for (size_t i = 0; i < POOL_SIZE - 1; i++) {
            pool[i].next = &pool[i + 1];
        }
        pool[POOL_SIZE - 1].next = nullptr;
        
        freeList = pool;
    }
    
public:
    using value_type = T;
    
    PoolAllocator() {
        allocatePool();
    }
    
    ~PoolAllocator() {
        for (Block* pool : pools) {
            ::operator delete(pool);
        }
    }
    
    T* allocate(size_t n) {
        if (n != 1) {
            throw bad_alloc();
        }
        
        if (!freeList) {
            allocatePool();
        }
        
        Block* block = freeList;
        freeList = freeList->next;
        
        return &block->data;
    }
    
    void deallocate(T* ptr, size_t n) noexcept {
        if (n != 1) return;
        
        Block* block = reinterpret_cast<Block*>(ptr);
        block->next = freeList;
        freeList = block;
    }
};

int main() {
    list<int, PoolAllocator<int>> myList;
    
    for (int i = 0; i < 1000; i++) {
        myList.push_back(i);  // 풀에서 할당
    }
}

예시 2: 스택 할당자

template<typename T, size_t N>
class StackAllocator {
private:
    alignas(T) char buffer[N * sizeof(T)];
    size_t used = 0;
    
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        if (used + n > N) {
            throw bad_alloc();
        }
        
        T* result = reinterpret_cast<T*>(buffer + used * sizeof(T));
        used += n;
        return result;
    }
    
    void deallocate(T* ptr, size_t n) noexcept {
        // 스택 할당자는 해제 안함 (스코프 종료 시 자동)
    }
};

int main() {
    // 스택에 할당
    vector<int, StackAllocator<int, 100>> v;
    
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    
    // 스코프 종료 시 자동 해제
}

예시 3: 추적 할당자

template<typename T>
class TrackingAllocator {
private:
    static size_t allocCount;
    static size_t deallocCount;
    static size_t bytesAllocated;
    
public:
    using value_type = T;
    
    T* allocate(size_t n) {
        allocCount++;
        bytesAllocated += n * sizeof(T);
        
        cout << "할당: " << n * sizeof(T) << " bytes" << endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }
    
    void deallocate(T* ptr, size_t n) noexcept {
        deallocCount++;
        bytesAllocated -= n * sizeof(T);
        
        cout << "해제: " << n * sizeof(T) << " bytes" << endl;
        ::operator delete(ptr);
    }
    
    static void printStats() {
        cout << "할당 횟수: " << allocCount << endl;
        cout << "해제 횟수: " << deallocCount << endl;
        cout << "현재 사용: " << bytesAllocated << " bytes" << endl;
    }
};

template<typename T>
size_t TrackingAllocator<T>::allocCount = 0;

template<typename T>
size_t TrackingAllocator<T>::deallocCount = 0;

template<typename T>
size_t TrackingAllocator<T>::bytesAllocated = 0;

int main() {
    {
        vector<int, TrackingAllocator<int>> v;
        
        for (int i = 0; i < 100; i++) {
            v.push_back(i);
        }
    }
    
    TrackingAllocator<int>::printStats();
}

PMR (Polymorphic Memory Resources)

#include <memory_resource>

int main() {
    // 단조 버퍼
    char buffer[1024];
    pmr::monotonic_buffer_resource pool(buffer, sizeof(buffer));
    
    // PMR 벡터
    pmr::vector<int> v(&pool);
    
    for (int i = 0; i < 100; i++) {
        v.push_back(i);  // buffer에서 할당
    }
}

자주 발생하는 문제

문제 1: 할당자 비교

// ❌ 할당자 비교 안함
template<typename T>
class BadAllocator {
    // operator== 없음
};

// ✅ 할당자 비교 구현
template<typename T>
class GoodAllocator {
    // ...
};

template<typename T, typename U>
bool operator==(const GoodAllocator<T>&, const GoodAllocator<U>&) {
    return true;
}

문제 2: 할당자 전파

// 컨테이너 복사 시 할당자 전파 여부
template<typename T>
struct allocator_traits<MyAllocator<T>> {
    using propagate_on_container_copy_assignment = true_type;
    using propagate_on_container_move_assignment = true_type;
    using propagate_on_container_swap = true_type;
};

문제 3: 정렬되지 않은 메모리

// ❌ 정렬 고려 안함
char buffer[100];
int* ptr = reinterpret_cast<int*>(buffer);  // 정렬 안됨!

// ✅ alignas 사용
alignas(int) char buffer[100];
int* ptr = reinterpret_cast<int*>(buffer);

FAQ

Q1: 커스텀 할당자는 언제 사용하나요?

A:

  • 메모리 풀
  • 특수 메모리 영역 (GPU, 공유 메모리)
  • 메모리 추적/디버깅
  • 성능 최적화

Q2: 성능 이점은?

A: 메모리 풀 사용 시 할당/해제가 10-100배 빠를 수 있습니다.

Q3: PMR이란?

A: C++17의 다형적 메모리 리소스. 런타임에 할당자를 변경할 수 있습니다.

Q4: 할당자 구현은 어렵나요?

A: 기본 구현은 간단하지만, 완전한 구현은 복잡합니다. allocator_traits를 활용하세요.

Q5: 할당자 디버깅은?

A:

  • 추적 할당자 사용
  • Valgrind
  • AddressSanitizer

Q6: Allocator 학습 리소스는?

A:

  • cppreference.com
  • “The C++ Standard Library” (Nicolai Josuttis)
  • Boost.Pool 소스 코드

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Custom Allocator | “커스텀 할당자” 가이드
  • C++ PMR | “다형 메모리 리소스” 가이드
  • C++ Stack Allocator | “스택 할당자” 가이드

관련 글

  • C++ vector reserve vs resize |
  • C++ Custom Allocator |
  • C++ Algorithm Copy |
  • C++ Algorithm Count |
  • C++ Algorithm Generate |