이클립스 CDT 디버거를 통해 C++ STL 컨테이너의 내용을 보면 한눈에 봐서는 알 수 없는 난해한 구조와 값들로 가득차 있다. 하지만 pretty-printing을 통해 std::map, std::list, std::vector 등의 컨테이너 안에 있는 내용을 보다 가독성이 높은 형태로 볼 수 있다.


본 포스팅은 http://wiki.eclipse.org/CDT/User/FAQ#How_can_I_inspect_the_contents_of_STL_containers.3F 에서 제공하고 있는 내용을 한글로 간단하게 번역하고 적용하면서 겪었던 몇몇 참고 사항을 추가했다. 보다 정확한 정보를 원하시는 분은 위 링크를 직접 확인해 보시는 것도 좋은 방법이다.


Pretty-printing 요구사항 :

 - GDB 7.0 버젼 이상

 - python

 - pretty-print 소스 코드


Pretty-printing을 위해 GDB 설정하기


1. 파이썬이 설치 되어 있어야 한다.

svn을 통해 Python pretty-printers 를 체크아웃 받는다.

svn co svn://gcc.gnu.org/svn/gcc/trunk/libstdc++-v3/python

개인적으로는 프로젝트 하위 폴더에 받아 각각 관리하는 것도 좋지만 /usr/local 과 같은 시스템 디렉토리에 받아 놓고 프로젝트에 참여 하는 모든 사람들이 같은 패스를 가지게 하는것도 괜찮은 방법이라 생각한다.


2. GDB에게 pretty-print의 위치를 알려주기 위해 아래와 같이 .gdbinit을 작성한다.

python

import sys

sys.path.insert(0, '/home/user_name/gdb_printers/python') #svn에서 체크아웃 받은 디렉토리를 적어준다

from libstdcxx.v6.printers import register_libstdcxx_printers

register_libstdcxx_printers (None)

end

참고적으로 sys.path에 등록되는 디렉토리 경로는 절대 경로여야 하며 프로젝트 마다 매번 경로를 설정해주는 번거로움을 줄이기 위해 다음(http://stackoverflow.com/questions/8663076/python-best-way-to-add-to-sys-path-relative-to-the-current-running-script) 과 같은 방법을 써봤지만 정상적으로 인식을 하지 못한다. 공동 프로젝트를 각 개인이 개발하는 형식이라면 각각 .gdbinit을 셋팅해야 한다(방법을 알면 가같이 공유 좀..)


3. CDT에게 .gdbinit 파일이 어디있는지 알려줘야 한다. 

기본 경로는 해당 프로젝트의 루트 경로지만 이클립스 메뉴의 Windows->Preferences->C/C++->Debug->GDB에서 경로를 수정 할수도 있다.


참고 :

gdb가 무한루프에 빠지며 hang 되는 상태가 발생하는 버그가 있다고 한다(https://sourceware.org/bugzilla/show_bug.cgi?id=12555)

이럴 땐 <pretty_print_root_dir>/libstdcxx/v6/printers.py 파일 StdStringPrinter 클래스 to_string 함수 내에서 len 변수의 값을 적절한 크기로 제한 해주면 된다.

if len > 100:

len = 100

 

Posted by kukuta

댓글을 달아 주세요

/**
  예전에 한번 보긴 했었지만, 거의 C style의 type casting만을 사용하다 보니 내 머리 속에서 점점 잊혀져 가고 있다.
  C style의 type casting이 사용하기도 쉽고, 자유도가 높긴 하지만 여러가지 오류의 소지도 있고, 코드도 지저분하게 만든느 경향이 있으므로 앞으로는 C++ style의 type casting을 사용 하기로 결심. 이렇게 다시 한번 정리 한다.
*/

* static_cast
 static_cast는 C에서의 type casting의 제한된 버젼이라고 생각하면 된다. 다만 C와의 차이점은 상속 트리 내에서의 포인터 형 변환만이 가능하다는 것이다.
 즉, 상속 트리 내에서 부모나 자식간으로의 포인터 casting은 가능하지만 전혀 관계가 없는 type으로 casting 하고자 하면 컴파일 타임에 오류가 난다.
class A {
};

class B : public A {
};

class C {
};

A* a = new A;

// B는 A의 자식이므로 casting이 가능하다. 또한 그 반대로도 가능하다.
B* b = static_cast<B*>(a);
A* aa = static_cast<A*>(b);

// 컴파일 오류
C* c = static_cast<C*>(a);

한가지 참고 할 것으로, static_cast는 다이아몬드형 상속구조에서는 정상적으로 동작하지 않는다. 예를 들어 아래와 같은 코드를 살펴 보자 :
class A {
public :
    virtual ~A() {}
};

class B : public A {
public :
    virtual ~B() {}
};

class C : public A {
public :
    virtual ~C() {}
};

class D : public B, C {
public :
    virtual ~D() {}
};

D* d1 = new D();
A* a1 = static_cast<A*>(d1);

A* a2 = new A();
D* d2 = static_cast<D*>(a2);
위의 코드에서 컴파일러는 D의 기반 클래스인 A를 어떤 것으로 정해야 할지 갈피를 잡지 못하고 에러를 발생 시키고 만다. 이럴 경우에는 아래에서 소개될 dynamic_cast를 사용해야만 한다.

* const_cast

 const_cast는 서로 다른 형간의 변환을 수행하지는 못하지만 대신 const가 아니었던것을 const로 만들거나, const였던것을 non-const로 만들 수 있다. 하지만 일반적으로 non-const type을 const로 바꾸는 경우는 거의 없으며 그리 제한적인 변경이 아니므로 자동적인 type casting이 일어난다. 하지만 const를 non-const type으로 변경 하는 것은 명시적으로 지정 해 주어야만 한다.
class A {
};

class B : public A{
};

int main() {

   A* a = new A;
   // 컴파일 오류 invalid const_cast from type `A*' to type `B*'
   B* b = const_cast<B*>(a);

    const int ci = 10;
    int* pi = const_cast<int*>(&ci);
    *pi = 1000;

    std::cout << pi << " " << *pi << std::endl;
    std::cout << &ci << " " <<  ci << std::endl;
}
※ 신기한 것은 같은 주소 공간에 서로 다른 값을 가지고 있다는 것이다. 이것을 어떻게 설명 해야 하는 것인고..

* reinterpret_cast

C style type casting과 똑같은 위력을 발휘한다. type, 상속 관계에 관계 없이 어떤 내장형, 어떤 포인터형이든지 다 casting이 가능하다.
※ 각종 예외상황들은 해당 컴파일러에 따라 다르게 처리된다.

* dynamic_cast
다른 casting과 dynamic_cast의 차이점은 다른 여타 cast 연산자들은 컴파일 타임에 컴파일러에 의해 평가되며, 정상적인 컴파일 또는 오류로 결과가 나타난다. 하지만 dynamic_cast는 casting의 가능 여부를 런타임시에 평가하며 내장 테이터 형을 제외한 포인터나 참조에만 이용 할 수 있다.

dynamic_cast는 static_cast와 달리 두 포인터가 동일한 상속 트리에 있는지 확인하지 않으며 포인터가 참조하는 개체의 실제 형을 확인하고 변환이 가능한지를 확인한다. 만일 가능하다면 다중 상속을 처리하기 위한 오프셋 계산까지 마친 새로운 포인터를 리턴하고, 불가능 하다면 NULL 포인터를 리턴한다.

보다 자세한 설명을 위해 아래의 예를 살펴 보도록 하자 :
class Parent
{
public :
    virtual void polymorphic() = 0;
};

class DeriveA : public Parent
{
public :
    virtual void polymorphic() {
    }
};

class DeriveB : public Parent
{
public :
    virtual void polymorphic() {
    }
};

int main(int argc, char** argv)
{
    Parent* p =  new DeriveA;
    DeriveB* db = dynamic_cast<DeriveB*>(p);
    if(NULL == db)
    {
        std::cout << "It is not a instance of DeriveB" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveB" << std::endl;
    }

    DeriveA* da = dynamic_cast<DeriveA*>(p);
    if(NULL == da)
    {
        std::cout << "It is not a instance of DeriveA" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveA" << std::endl;
    }

    return 0;
}
※ dynamic_cast를 사용하기 위해서는 컴파일러의 RTTI(real time type infomation) 옵션을 켜놓아야 한다.

위의 코드를 컴파일 하여 결과를 살펴 보면 :
It is not a instance of DeriveB
It is a instance of DeriveA
와 같이 나올 것이다.

요약을 하자면,  DeriveA의 인스턴스는 다형성을 이용하기 위해 부모 클래스의 포인터를 이용하고 있고, dynamic_cast는 런타임시에 어떤 자식 클래스의 인스턴스인지 부모 클래스의 포인터를 통해서 알아 낼 수 있는 것이다.

다이나믹 캐스트에 대한 보다 자세한 설명은 아래 링크 참조 :
http://www.cprogramming.com/reference/typecasting/dynamiccast.html
Posted by kukuta
TAG C/C++, STL

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc/ BlogIcon 깜쏘 2007.01.25 21:33  댓글주소  수정/삭제  댓글쓰기

    중요한 건, 타이핑 하기 힘들게 만들어서 못 쓰게 하려는 외계인의 수작임...

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2007.01.26 11:03 신고  댓글주소  수정/삭제

      reinsterprete_cast 외에는 그렇게 짜달시리 어려운것도 없잖아..ㅋㅋ
      어차피 그거야 C style하고 똑같으니까 따로 쓸 필요 없고..
      내가 보기에는 니 게으름이 가독성을 가로막고 있는것 같아..ㅋㅋ

이번 장에서는 STL의 string을 이용해 특정 구분자로 나뉘어 있는 텍스트 한줄을 여러개의 단어들로 쪼개는 함수를 만들어 보도록 하겠습니다.

      1 void split(const string& text, string& separators, vector<string>& words) {
      2     int n = text.length();
      3     int start, stop;
      4
      5     start = text.find_first_not_of(separators);
      6     while ((start >= 0) && (start < n)) {
      7         stop = text.find_first_of(separators, start);
      8         if ((stop < 0) || (stop > n))
      9             stop = n;
     10         words.push_back(text.substr(start, stop - start));
     11         start = text.find_first_not_of(separators, stop+1);
     12     }
     13 }

이 함수는 text 문자열에서 separators와 매칭되는 구분자를 찾아 words 벡터에 저장합니다. 직접적인 리턴은 없지만 파라메터를 통해 리턴을 하는 구조입니다.

5 라인의 'find_first_not_of' 함수는 'separators'와 매칭되지 않는 가장 첫번째 위치를 찾습니다. 이 함수의 프로토 타입은 뒤에 어디서 부터 시작 할지 위치를 정해주는 파라메터가 하나 더 있는데 기본인자로 '0'이 지정되어 있어 아무것도 지정하지 않게 되면 처음부터 검색을 시작합니다.

그리고 루프를 돌면서 그 뒤에 나타나는 구분자에 속하는 문자열을 찾습니다. 바로 7라인의 'find_first_of'라는 함수가 하는 역할 입니다. 이 함수에서 리턴되는 것은 이름 그대로 'separators'와 매칭되는 문자열의 가장 처음 위치를 리턴하는 것이지요.

이렇게 문자열의 시작 부터 구분자의 앞 까지 위치를 알아 내었습니다. 이제 해야 할 일은 문자열의 시작 부터 구분자까지 끊어 내어 벡터에 저장하는 일이겠지요. 이것을 위해 10 라인의 'substr' 함수가 사용되었습니다. 이 함수에서 리턴 되는 값을 벡터에 저장하는 것 까지.. 이 과정을 문자열이 끝날 때 까지 반복하는 것입니다.

Ref.
 stl reference : http://www.sgi.com/tech/stl/table_of_contents.html
 code : http://oopsla.snu.ac.kr/~sjjung/stl/

Posted by kukuta

댓글을 달아 주세요

/**
  요즘들어 포스팅 하는 주기가 점점 길어 지고 있네요.
  날씨는 점점 추워지고 솔로들이 더욱 살아 남기 힘들어지는 크리스마스
  가 다가 오고 있습니다.

  저도 크리스마스 때 남들처럼 거리를 활보 하고 싶습니다만 겨울의 거리
  는 솔로에겐 냉정하지요.

  올 크리스마스는 징검다리 휴가라, 크리스마스 이브에 휴가를 내면 4일
  을 연속으로 쉴 수 있군요. 하지만 어디 딱히 갈 곳도 없고 오라는 곳도
  없으니, 저는 웹 서핑이나 하면서 블로그 포스팅이나 하렵니다. 
*/

오늘은 stl의 map의 erase에 대해서 간략하게 알아 보겠습니다.
보통 다른 stl의 컨테이너들은 erase를 하는 것에 대해 별다른 신경을 쓰지 않아도 별 문제가 없지만 map이란 녀석은 다른 컨테이너들 처럼 다루면 성질을 부리며 런타임 오류를 내버리는 경우가 있습니다(어쩌면 아무런 오류도 내지 않고 묵묵히 자기 할 일을 할 수도 있습니다만...정상적인 반응은 아닙니다).

위에서 언급 했지만, map 컨테이너의 반복자가 참조하고 있는 원소가 삭제되는 경우 큰 위험성이 있습니다. 예를 들어,

typedef std::map<std::string, float> StringFloatMap;
StringFloatMap coll;
StringFloatMap::iterator pos;
....

for(pos = coll.begin(); pos != coll.end(); pos++) {
    if(pos->second == value) {
        coll.erase(pos);  // 런타임 에러
    }
}

coll.erase(pos) 이후에 coll의 반복자인 pos는 무효화 됩니다. 해당 반복자는 erase에 의해 이미 지워진 요소를 가리키고 있으므로 ++연산자는 정의 되지 않은 결과를 불러 옵니다(위에서 정상적으로 동작 할 수 있다고도 했지요? 정의 되지 않은 결과라서 그런겁니다).

이런 무효화를 방지 하기 위해서는 erase를 호출하기 전에 미리 반복자를 복사하고 erase를 호출 하면 됩니다.

typedef std::map<std::string, float> StringFloatMap;
StringFloatMap coll;
StringFloatMap::iterator pos;
....

for(pos = coll.begin(); pos != coll.end();) {
    if(pos->second == value) {
        coll.erase(pos++);
    }
    else {
        ++pos;
    }
}

coll.erase(pos++)은 다음 원소를 참조하기 위해서 pos를 증가 시킵니다. 하지만 ++ 이라는 것은 내부적으로 복사 하고 증가를 하는 것이기 때문에 pos는 erase에 의해 제거된 원소를 참조 하지 않고, 복사된 것을 참조하게 되므로써 안전하게 증가 할 수 있습니다.

Ref.
 C++ Standard Libarary : Addison Wesley
Posted by kukuta
TAG C/C++, STL

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2007.12.13 19:13  댓글주소  수정/삭제  댓글쓰기

    당연한 것을...
    저건 open하지 않은 파일포인터 close하는 것과 같은 격입니다.
    그런데 저는 for보다는 while이 편해서, 저런 식의 실수는 거의 없을 듯^^

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2007.12.13 20:06 신고  댓글주소  수정/삭제

      그래? 나는 while보다는 for를 더 선호하는데...

      ㅎㅎ 그리고 물론 알고 있다면 저런 실수를 잘 하지 않겠지..하짐만 아는 사람보다는 모르는 사람이 더 많을 것 같아서 포스팅 했어..

      그래..사실 할게 없어서 그래..ㅠㅠ

    • Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2007.12.14 15:20  댓글주소  수정/삭제

      근데 선배...
      어셈코드를 보면 for보다는 while이 빨라요.
      하짐만 => 하지마 입니다.

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2007.12.14 20:02 신고  댓글주소  수정/삭제

      ㅇㅇ 하짐만 가독성이 더 좋잖아.
      흐음 나만의 생각인가?

      그리고 하짐만은 서울 말이야..후훗..(뜨끔!)

  2. Tiger Hong 2011.01.17 13:50  댓글주소  수정/삭제  댓글쓰기

    잘 봤습니다.
    Windows server 2003 에서만 에러가 생겨서 찾다보니 올려주신 내용이었습니다.
    큰 도움이 되었네요... 감사합니다~ ^^

  3. blueasa 2011.12.08 18:49  댓글주소  수정/삭제  댓글쓰기

    좋은 포스팅 감사합니다. :)

  4. dxd 2015.01.16 09:35  댓글주소  수정/삭제  댓글쓰기

    좋은 포스팅 감사합니다. :)