정말 대단한 버그다!
회사 동료 분이 메모리 누수 현상이 있다길래 둘이 같이 달려들어 문제를 찾아봤는데, 천신만고 끝에 원인을 알아냈다.
wcout.imbue( locale("korean") );
wostringstream os;
for(int i=0; i<2; i++)
{
os << _T("하이, ");
os.flush();
wcout << os.str() << endl;
}
이렇게 짜놓고 내가 기대하는 바는 당연히
하이, 하이,
이럴 것이다. 하지만 실제 결과는 이와는 완전히 다르다.
하이, 하이, 하이,
한마디로 flush가 flush 답게 작동하지 않는다는 게 문제다. 결국 flush가 되지 않고 문자열이 쌓이다 보니 메모리 누수가 일어나는 듯 보였던 것이다. 해결책을 보면 더 웃긴데, 솔직히 stringstream을 더 이상 쓰고 싶지 않을지도 모른다.
wcout.imbue( locale("korean") );
wostringstream os;
for(int i=0; i<2; i++)
{
os << _T("하이, ");
os.flush();
wcout << os.str() << endl;
}
os.~wostringstream();
new ( (void *) &os ) std::wostringstream;
wcout << _T("Start") << endl;
wcout << os.str() << endl;
wcout << _T("End") << endl;
이렇게 하면 기대하던 결과가 나온다.
하이, 하이, 하이, Start End
황당한 해결책을 알려준 구세주 Gianni Mariani씨 왈,
The “str()” and “str( string )” methods should work.
However, on some platforms these may not work so you may use the ugly reconstruct hack.
Author Details
Kubernetes, DevSecOps, AWS, 클라우드 보안, 클라우드 비용관리, SaaS 의 활용과 내재화 등 소프트웨어 개발 전반에 도움이 필요하다면 도움을 요청하세요. 지인이라면 가볍게 도와드리겠습니다. 전문적인 도움이 필요하다면 저의 현업에 방해가 되지 않는 선에서 협의가능합니다.
위의 내용은 버그가 아닌것 같은데요? -_-;
ofstream의 경우와 비교해보면 ofstream은 파일을 출력으로 하는 stream이고 여기에 operator<<을 사용하면 해당 내용이 버퍼에 쌓이게 되죠. 그리고 flush()하면 버퍼에 있던 내용이 출력으로 사용되는 파일에 flush되고요. 이때 flush 한다고 해서 이전에 파일에 썼던 내용이 지워지지 않죠.
ostringstream은 (wostringstream도 동일하겠지만) string을 출력으로 하는 stream이고 여기에 operator<<을 사용하면 해당 내용이 버퍼에 쌓이게 됩니다. (ostringstream의 경우 별도의 버퍼를 사용하는지는 모르겠지만요. ^^;) 그리고 flush()를 하게 되면 버퍼에 쌓여 있던 내용이 출력으로 사용되는 string에 flush되고요. 물론 이전에 string에 썼던 내용은 지워지지 않고 계속 쌓이게 되죠.
따라서 위와 같이 출력되는 것이 정상이 아닌가 싶습니다. 위의 코드에서는 os.str(”“)을 사용하여 내부 string 버퍼를 초기화하는 방법도 있겠지만 ostringstream 객체를 for문 안에 선언하는 방법이 제일 깔끔해 보입니다.
참고로 저희는 예전에 ostringstream이 나오기 전부터 있었던 ostrstream을 잘못 사용하여 메모리 릭이 발생했던 적이 있었는데 역시 찾는데 애 좀 먹었더랬죠. ^^;;
저도 윗분 말씀대로 버그가 아닌듯 보여지는데요..^^;
네. 버그는 아닌가 보네요. 제가 잘못 알았네요. 정석은 이렇게 하는 것 같습니다.
os.clear();
os.str(_T(”“));
네… 근데 clear()는 error state flags를 셋팅하는거라 굳이 호출할 필요는 없을 것 같습니다. ^^
그런데 혹시 사용하시는 블로깅 툴이 WordPress이신가요? 댓글밑에 나오는 Captcha나 Notify me… 기능이 탐나서요. ^o^;
clear()를 치는 걸 권하는 STL 문서가 있어서 일부러 그렇게 했습니다.
제 블로그 엔진은 expressionengine 인데, 커뮤니티 기능을 뺀 무료 버전도 있습니다. 사실 무료 버전만으로 충분하기 때문에 그쪽으로 갈아탈까 싶기도 합니다만, 가격이 얼마 안 해서 유료 버전을 쓰고 있습니다. 한국에서 만든 게 아니라서 가끔 한글 문제를 겪기도 하지만, 대부분 해결한 상황이고, 유연한 기능이 아주 훌륭해서 마음에 드실 겁니다. 기능이 너무 많아서 아직도 제대로 못 쓰고 있긴 합니다만…
테스트 결과를 보니 os.str(_T(”“))와 본문에 쓴 방법은 차이가 있네요. 문자열 병합을 하다 보니 stringstream 인스턴스에 500바이트가 할당된 상황이라고 해보죠. 본문에 적힌대로 소멸자와 생성자를 호출하면 버퍼가 원래 크기로 돌아옵니다. 하지만 os.str 을 사용하면 버퍼는 500바이트 그대로이고, 단 위치를 가리키는 포인터만 맨 앞으로 돌아오는 듯 합니다.
경우에 따라 두 가지 전략을 적절히 쓰면 좋을 듯 하네요. ^^
1. flush()를 오해하신 듯 합니다.
콘솔/파일 스트림의 경우 i/o 횟수가 부담이 되니 메모리에 버퍼를 두고 일정량이 쌓이면 실제 i/o를 하게 되는데, 그 버퍼를 비우면서 i/o를 강제하는 것이 flush()입니다.
애초에 메모리에 기록하는 것이 목적인 ostringstream은 flush()가 필요 없지요. 구현도 아마 아무것도 안하고 리턴할 것입니다.
2. 소멸자,생성자를 호출하려면 그냥 for블럭 안에 ostringstream을 선언하면 컴파일러가 알아서 다 해주겠지요. 저런 변태 코드를 쓸 필요 없이요.
1. std의 stringstream 구현은 flush가 아무 일도 안하는 듯 하더군요. 하지만 다른 쪽 언어나 라이브러리에선 stringstream 역할을 하는 클래스가 제가 말한 방식대로 작동하기도 합니다. 직관적으로 보기엔 flush를 치면 비우는 게 맞지 않나요? 이 문제 관련한 질의 글이 많은 것만 봐도 설계상 고려해볼만한 문제인 것 같습니다.
2. stringstream을 블럭 안에서 쓰는 경우도 있겠지만, 그렇지 않은 경우도 있죠. 클래스 멤버로 들어갔을 때 말이죠. 동적 할당하고 소멸시키면 되지 않느냐라고 볼 수도 있겠지만요.
또 하나는 왜 for 블럭 안에서 ostringstream을 선언하는지 이해가 잘 안 되네요. 보통 for 문에서 문자열을 조합해야 하는 경우라면, 그리 크지 않은 문자열일 텐데요. 물론 그렇지 않은 경우도 있겠지만요.
같은 문제로 고민하고 있었는데 코멘트 덕분에 좋은 정보를 얻었네요~