1. Old. Old. Old style

char buffer[1024];
...(기타 여러 코드들)...
sock.recv(buffer, 1024);

혹시 위와 같은 스타일의 코드를 작성하고 있지는 않으신가요? 물론, 잘 작동하고 문제가 없는 코드일겁니다. 하지만, 여기엔 문제가 하나 있습니다. buffer는 배열이지 객체가 아닙니다. iterator도 없으며, 멤버함수도 없습니다. 불편할 뿐더러 위험합니다. 한번 볼까요?

char buffer[1024];
...(기타 여러 코드들. 복잡해서 눈에 잘 안들어옴)...
sock.recv(buffer, 4096); // 뭔가의 필요에 의해 고쳐졌군요.

와우. 망했습니다. 스택이 깨지면서 어떤 문제가 발생할지 감도 잡기 힘듭니다. 주로 보안사고가 이런 코드에서 많이 발생합니다. buffer overflow죠. 이런 류의 “실수”를 하지 않는다고 보장할 수 있을까요? 네. 이런걸 막기 위해 사람들은 이렇게 작업하기도 합니다.

#define BUFFER_SIZE 1024
char buffer[BUFFER_SIZE];
...(기타 여러 코드들. 복잡해서 진짜 눈에 잘 안들어옴)...
sock.recv(buffer, BUFFER_SIZE);

별 문제 없어보입니다. 하지만, 버퍼의 크기가 각 위치마다 달라져야 한다면.. 그 수만큼 #define도 늘어날겁니다. enum을 쓰더라도 이건 매한가지죠.

그렇다면, 이런건 어떨까요.

std::vector<char> buffer;
buffer.resize(1024);
sock.recv(&buffer[0], buffer.size());

훌륭하죠. 이제 C++답습니다. 하지만, buffer는 여전히 stack변수이며 이 부분이 실행될때마다 매번 새로 만들어질겁니다. 그리고, 매번 새로 만들어진다는건… 매번 할당자를 호출해야한다는 겁니다. (쉽게말해 new/malloc이 매번 호출됩니다. 재앙이죠.)

이걸 막기 위해선, 그렇죠. buffer를 재사용하면 됩니다. 하지만, 재사용시에는 언제나 멀티쓰레드 환경을 고민해야하는 상황이 다가옵니다. 후.

좀 더 C++다우면서, 배열과 같은 효과를 내는건 불가능할까요?


2. olleh! array!

boost::array<char, 1024> buffer;
sock.recv(&buffer[0], buffer.size());

네. 완벽하게 C++ container스러우면서, 배열의 역할을 수행하는 자료구조입니다. boost::array이죠! 이녀석의 내부구조는 다음과 같습니다.

template<class T, int N>
class array
{
    ...(생략)...
    T elems[N];
};

유일하게 갖고 있는 멤버변수는 elems. 배열입니다! 결국 배열을 선언한 것과 동일한 효과를 갖습니다. 여기까지 왔으면, 파직 하고 뭔가 옵니다.

array는 iterator, const_iterator, size(), begin(), end(), front(), back(), empty()등의 STL컨테이너라면, 다들 갖고 있는 형식과 함수들을 갖추고 있습니다. 그러면서도 []연산자를 지원하여 기존의 배열과 흡사하게 사용할 수 있지요. 이것이 시사하는 바는 생각보다 큽니다.

기존의 배열에 알고리즘을 적용해봅시다.

char buffer[1024];
...(생략)...
char* found = std::find(buffer, buffer+1024, 'c');

위와 같은 알고리즘을 적용할 경우, 1024라는 크기상수가 지속적으로 따라다닙니다. 또한, buffer를 vector나 다른 자료구조로 바꾸기라도 하면, 꽤 많은 수정을 가해야하지요. 하지만, boost::array를 사용한다면 다음과 같이 바뀝니다.

// 편의를 위한 typedef. C++ 프로그래머의 친구죠. 🙂
typedef boost::array<char, 1024> buffer_type;
buffer_type buffer;
buffer_type::iterator found =
    std::find(buffer.begin(), buffer.end(), 'c');

3. 정리

사실, boost::array(TR1에 들어갔으니 이젠 tr1::array려나요.)는 굉장히 간단하고, 의미없게 느껴질 수 도 있습니다. 하지만, 과거의 배열은 원소들을 담는 컨테이너의 역할을 함에도 불구하고 STL스럽지 않다는 단점이 있었습니다. 자료구조를 다루는데 있어서, STL 컨테이너들과 굉장히 동떨어진 사용법은 코드의 일관성을 해치게 됩니다.

이런 면에서 볼 때, 배열과 동일한 구조/성능을 가지고 있으면서 STL 컨테이너와 유사한 인터페이스를 갖춘 array는 C++내에서 C의 유산에 의해 코드가 망가지는 것을 피할 수 있도록 해주는 상당히 중요한 도구입니다. 🙂 그리고, 200줄이 안되는 이 코드는 좋은 참고자료이기도 합니다. 시간 나시면 꼭 한번 읽어보시길. 🙂


A. References
boost::array의 문서

boost::array를 배워봅시다.
태그:         

boost::array를 배워봅시다.”에 대한 2개의 생각

  • 2009/12/01 2:45 오후
    고유주소

    boost::array buffer;

    이렇게 선언해도 stack 변수인것은 마찬가지 아닌가? 매번 호출될 때마다 할당되는 문제는 heap에 잡는거 말고 어케… 방법이?

    응답
  • 2009/12/01 4:11 오후
    고유주소

    ㅇㅇ 스택변수인건 매한가지지. 근데 체감상 heap보단 stack이 더 빨리 잡히는듯? 뭐 할당받는게 싫으면 vector의 cache line을 짜놓고 쓰고나서 cache line에 넣을때 clear만 호출해서 할당받았던 메모리를 유지하며 작업하는 것도 한 방법임. 실제로, 회사에서 써봤는데 나쁘지 않은 성능이 나온다오. (여차하면 그 cache line을 TLS화 해버리면 더 굳. 하지만, 내가 짜는건 대부분 Producer-Consumer라서 할당받는 곳과 해제하는 곳이 다르므로 OTL)

    응답

답글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.