Skip to content


Quiz: C++ casting 연산자

사실 오늘의 삽질이라고 제목 붙여야 하지만(?).

아래처럼 생겨먹은 클래스 계층이 있다고 치자.

struct B
{
    int b_x;
    B() : b_x(-1) {}
};
struct D : public B
{
    D() : B() {}
    virtual int Get() const = 0;
};
struct C : public D
{
    virtual int Get() const { return 3; }
};

이제 아래 코드를 실행한다고 하자.

C* c = new C();
printf("%8p %d %d\n", c, c->b_x, c->Get() );

띄워놓은 터미널에서 실행시킨 결과,

0x8a2d008 -1 3

가 나왔다. 다른 운영체제/하드웨어에서 해봐도 주소값 바뀌는 거 빼곤 비슷한 결과가 나온다.

여기서 문제, 여기서 printf 부분을 다음과 같은 함수로 빼냈다.

void func( B* b )
{
    printf("%8p %d %d\n", /* some casting */ b, b->b_x, /* some casting */ b->Get() );
}

...
// in main
func( /* some casting */ c );
...
이 부분이 처음 했던 것처럼 동작하려면, 어떻게 캐스팅 해야할까?

물론 이 문제 자체는 굉장히 특이한 상황에서 – 특히나 C/C++이 섞여있고, 콜백등으로 인해서 C 타입이 왔다 갔다하는 경우 – 나 보게 되긴 한다. 그렇지만 이 문제 자체는 C++ 타입 변환을 이해하는데 도움이 된다.

그래서 후보를 골라보면,

  • C-style 캐스트. (D*)로 처음 코드에서 처럼 변환
  • static_cast<D*> : C++에서 연관관계(pointer 내지는 암시적인 변환으로 호환되는)가 있는 타입 간에 컴파일 시간에 검사하여 (실행시간에는 안정성 검사없이) 변환하는 연산
  • dynamic_cast<D*> : C++에서 상속 관계를 따라 실행 시간에 검사해서 타입 변환하는 연산
  • reinterpret_cast<D*> : 아무런 해석없이1 있는 그대로 바꿔주는 변환연산.
  • const_cast<D*>: 상수성(const)나 volatile을 제거하려는 목적으로 쓰임. 여기선 불가능하지만 일단 리스팅.

들이다. 이걸 써서, 혹은 양쪽에 서로 다른 캐스팅을 적당히 조합해서 어떻게 써야, 처음 봤던 결과를 func를 호출해서도 볼 수 있을까?

답은,

C-style cast 와 static_cast<D*> 다. 2 이유를 설명하자면,

우선 const_cast는 위에서 써 놓은 이유대로 여기서는 아무런 의미가 없다.

다음으로 dyanmic_cast. B 타입이 가상 함수(virtual function)을 갖지 않기에(=다형성이 없기에) dynamic_cast 자체가 불가능하다.

reinterpret_cast의 경우엔 상당히 흥미로운데, 이건 “아무런 해석(=변경)”을 하지 않는다.그래서, 위에서 실행한 머신에서 해보면,

  • 인자를 reinterpret_cast<B*> 한 후, 함수에서 reinterpret_cast<D*>하면 => 같은 주소, 134515072, segfault3 이라는 매우 재밌는 결과가 나온다
  • 인자를 static_cast<B*> 혹은 (B*)로 캐스팅하고(결과가 같다), 함수에서 reinterpret_cast<D*> 하면 => 원래 주소 + 4, –1 3 이 나온다. 주소 +4는 32bit 플랫폼이라 그렇다.4
  • 인자를 reinterpret_cast<B*>, 함수에서 static_cast<D*> 하면 => 원래 주소 – 4, 이상한 값(…), segfault 가 일어난다. 가상 함수 테이블(vftbl) 주소가 망가진 말로(…).

등을 볼 수 있다.

이런 일은 사실,

  • 가상 함수 때문에 숨겨진 vftbl 포인터 크기가 포함되어버리고,
  • 이에 대한 조절을 연관 타입 변환을 담당하는 static_cast나, 이를 쓰게 되는 C-style 캐스팅은 주소 변환이 제대로 이뤄지지만, (주소 +- 테이블 크기를 수행)
  • 값을 있는 그대로 보내주는 reinterpret_cast는 이를 처리하지 않아서 저런 괴악한 결과를 내는

것이다. reinterpret_cast 가지고 실험한 것은, 가상함수 테이블 위치를 잘못 잡았을 때 / 구조체 시작 주소를 잘못 계산할 때로 구분해서 해석하면 된다.

C++ 캐스팅 이젠 좀 이해가 되시는지?

  1. 난 이게 이름이랑 너무 안 어울린다고(?) 생각한다 []
  2. 사실 C-style cast는 일련의 C++ 캐스팅을 순서대로 (컴파일 시점에서) 시도해보는 것 뿐이다. 여기서는 그 순서에 따라 static_cast<D*>가 선택된다. 순서 자체는 stackoverflow.com의 이 답변을 참고하자. 덤으로 저 글에는 C++ 캐스팅 자체의 용법도 잘 설명되어 있다. []
  3. 두번째 값은 실행하는 플랫폼/C-runtime에 따라 다르다 []
  4. 물론 alignment에 따라 바뀔 순 있다 []
이 저작물은 별도로 명시하지 않은 경우, Creative Commons Attribution-Share Alike 3.0 Unported License에 따라 이용하실 수 있습니다.

Posted in Computer.

Tagged with , .


13 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Dish says

    으악 헷갈려 (…)
    콜 할 땐 그냥 암시적 캐스팅 되게 냅두는 게 좋지 않을까요

  2. ipkn says

    reinterpret_cast는
    전 오히려 새로운 해석을 부여한단 느낌에서 잘 와닿았었는데요.
    일단 이 경우는 부모 클래스로 가는 거니 안써줘도 잘 돌지 않나요?

  3. rein says

    Dish / 그러함

    ipkn / 부모 클래스에서 자식 클래스로도 가잖아?(함수 안에서) 그때는 필요하지
    근데 내가 생각한 ‘재해석’은 뭔가 변화가 있는건데 가시적인(?) 변화는 없지(?)

  4. 조프 says

    D로 충분하냐 C까지 내려가야하냐는 문젠줄 알고, 당연히 D면 되지… 라고 생각하고 내렸더니…;;

    • rein says

      아악 원래 의도는 D였는데. 수정해야겠네요 Orz.
      수정되었습니다.

  5. kane says

    reinterpret_cast 테스트 결과 중에 이해 안 되는 부분이 좀 있긴 한데,
    특히 세번째( “인자를 reinterpret_cast, 함수에서 static_cast 하면”)에서 원래 주소가 나와야 하는 거 아닌가요?

    • rein says

      reinterpret_cast<B*> 하면 원래 변수의 주소가 전달되고,
      static_cast<D*> 하면 해당 포인터 주소 – 4 를 해버려서 원래 주소가 아니라 괴악한 주소값(덕분에 가상 함수 호출이 segfault)으로 가버립니다.

  6. kane says

    하지만 주소값은 static_cast 하기 전에 출력한 거 아닌가요?

    • kane says

      답글을 이상한데 달았군요. ㅡ_ㅡ;

      • rein says

        kane / 그렇네요. 포인터 찍는 것에도 캐스팅하도록 수정했습니다.
        가지고 있는 코드는 그랬지만(…) 예제만들면서 그 부분은 제대로 못 넣었네요.

        ps. 근데 아이디가 낯이 익어서 해보는 소린데(…) SNAGs 란 곳을 아시나요?(…)

  7. kane says

    잘 알죠. 예상하고 있는 그 사람입니다. ㅎㅎ

    • kane says

      (이거 덧글 달기가 아직 적응이 잘 안되네…)

    • rein says

      kane 형이시군요! :$
      덧글 달기는 앞으로 많이 달아주시면 적응이 잘 될꺼에요(…)



Some HTML is OK

or, reply to this post via trackback.