Post thumbnail

스마트 포인터: 저수준에서 가비지 컬렉션을 추구하면 안 되는 걸까

· by 박승재

가비지 컬렉션(Garbage Collection)은 동적 할당된 메모리 영역 가운데 더 이상 사용하지 않는 영역을 탐지하여 자동으로 해제하는 기능입니다.

자바, C#, 파이썬 등의 프로그래밍 언어들은 처음부터 가비지 컬렉션 기법을 염두에 두고 설계되어, 언어 자체에 가비지 컬렉션이 포함되어 있습니다.

반면, C와 C++은 프로그래머가 직접 메모리를 관리하는 언어로 설계되어, 언어 자체에 가비지 컬렉션이 포함되어 있지 않습니다.

하지만, C와 C++의 문법적 기능과 컴파일러 확장을 이용하면 가비지 컬렉션 기능을 비슷하게나마 흉내 낼 수 있습니다.

C++에서의 스마트 포인터

C++의 RAII(Resource Acquisition Is Initialization) 기법을 이용하면 자동으로 사용하지 않는 메모리를 해지하는 스마트 포인터를 만들 수 있습니다.

template<typename T>
class Box {
public:
    explicit Box(T *ptr)
    : ptr(ptr) {}

    T *get() const {
        return ptr;
    }

    ~Box() {
        delete[] ptr;
    }

private:
    T *ptr;
};

클래스의 생성자에서 포인터를 받아 멤버 변수에 저장하고, 소멸자에서 저장한 포인터를 delete하면 됩니다.

void test_box() {
    Box<int> i{ new int };
    std::cout << i.get() << '\n';

    Box<int> a{ new int[100000000] };
    std::cout << a.get() << '\n';
}

test_box 함수가 종료될 때, 함수 내부에서 생성된 객체들의 소멸자호출되며 포인터가 자동으로 delete되는 원리입니다.

C++ STL에서는 이미 스마트 포인터에 대한 구현을 제공합니다.

  • std::unique_ptr는 위의 Box 형태와 동일한 기본적인 스마트 포인터입니다.

    복사가 불가능하기 때문에 다른 변수로 포인터를 이동시켜야 할 때는 std::move를 이용해야 합니다.

  • std::shared_ptr: 여러 변수동일한 포인터를 잡아둘 수 있는 스마트 포인터입니다.

    하나의 변수만 잡을 수 있는 std::unique_ptr와 달리, 여러 변수가 들고 있기 때문에 RC(Reference Counting) 방식을 이용합니다.

    따라서 RC로 인한 성능 손실이 발생합니다.

    또한 std::shared_ptr끼리 서로를 참조하는 순환 참조가 발생하면, 해당 메모리는 영원히 해지되지 않는 문제가 발생합니다.

  • std::weak_ptr: std::shared_ptr순환 참조막기 위해 사용하는 스마트 포인터입니다.

    순환되는 std::shared_ptr 대신 들어가, 약한(=Weak =Non-owning) 참조를 이용해 순환 참조를 끊어줍니다.

C에서의 스마트 포인터

C언어에는 클래스 문법이 존재하지 않기 때문에, 소멸자를 이용한 RAII를 통해 스마트 포인터를 만들 수 없습니다.

하지만, GNU C컴파일러 확장을 이용하면 비슷한 흉내를 낼 수 있습니다.

#define autofree __attribute__ ((cleanup(free_stack)))

__attribute__ ((always_inline))
inline void free_stack(void *ptr) {
    free(*(void **) ptr);
}

cleanup 속성(Attribute)은 변수가 스코프(Scope)를 벗어날 때, 주어진 함수를 호출합니다.

참고: GCC - Common Variable Attributes

int main(void) {
    autofree int *i = malloc(sizeof (int));
    *i = 1;
    return *i;
}

malloc으로 동적 할당imain이 종료될 때,

  1. __attribute__ ((cleanup(free_stack)))에 의해 free_stack 함수를 호출하게 되고,
  2. free_stack 안의 free 함수를 호출하여 자동으로 해지됩니다.

참고: Implementing smart pointers for the C programming language

libcsptr는 위 기법을 이용해 작성된 대표적인 C 스마트 포인터 라이브러리입니다.

그러나 C에서는 C++과 달리, 일반적으로 스마트 포인터를 사용하지 않습니다.

왜냐하면, cleanup이라는 GNU C 기능을 이용해 구현했기 때문에 컴파일러에 따라 동작하지 않는 경우가 있기 때문입니다.

대표적인 예로, Visual Studio의 기본 컴파일러인 MSVC에서는 cleanup지원하지 않습니다.

물론 Windows 사용자더라도 Clang이나 Intel C 컴파일러 등을 이용하면 아예 사용 불가능한 것은 아니나, 코드의 범용성을 위해 특정 컴파일러에서만 작동하는 코드는 지양하는 것이 올바르다고 생각합니다.

따라서 C언어에서의 스마트 포인터는 아예 불가능하지 않다고만 기억하면 될 것 같습니다.