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