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*>
: 아무런 해석없이 있는 그대로 바꿔주는 변환연산.const_cast<D*>
: 상수성(const)나 volatile을 제거하려는 목적으로 쓰임. 여기선 불가능하지만 일단 리스팅.
들이다. 이걸 써서, 혹은 양쪽에 서로 다른 캐스팅을 적당히 조합해서 어떻게 써야, 처음 봤던 결과를 func를 호출해서도 볼 수 있을까?
답은,
C-style cast 와 static_cast<D*>
다.1 이유를 설명하자면,
우선 const_cast
는 위에서 써 놓은 이유대로 여기서는 아무런 의미가 없다.
다음으로 dyanmic_cast
. B 타입이 가상 함수(virtual function)을 갖지 않기에(=다형성이 없기에) dynamic_cast
자체가 불가능하다.
reinterpret_cast
의 경우엔 상당히 흥미로운데, 이건 “아무런 해석(=변경)“을 하지 않는다.그래서, 위에서 실행한 머신에서 해보면,
- 인자를
reinterpret_cast<B*>
한 후, 함수에서reinterpret_cast<D*>
하면 => 같은 주소, 134515072, segfault2 이라는 매우 재밌는 결과가 나온다 - 인자를
static_cast<B*>
혹은(B*)
로 캐스팅하고(결과가 같다), 함수에서reinterpret_cast<D*>
하면 => 원래 주소 + 4, –1 3 이 나온다. 주소 +4는 32bit 플랫폼이라 그렇다.3 - 인자를
reinterpret_cast<B*>
, 함수에서static_cast<D*>
하면 => 원래 주소 – 4, 이상한 값(…), segfault 가 일어난다. 가상 함수 테이블(vftbl) 주소가 정상적이지 않아서.
등을 볼 수 있다.
이런 일은 사실,
- 가상 함수 때문에 숨겨진 vftbl 포인터 크기가 포함되어버리고,
- 이에 대한 조절을 연관 타입 변환을 담당하는
static_cast
나, 이를 쓰게 되는 C-style 캐스팅은 주소 변환이 제대로 이뤄지지만, (주소 +- 테이블 크기를 수행) - 값을 있는 그대로 보내주는
reinterpret_cast
는 이를 처리하지 않아서 저런 괴악한 결과를 내는
것이다. reinterpret_cast
가지고 실험한 것은, 가상함수 테이블 위치를 잘못 잡았을 때 / 구조체 시작 주소를 잘못 계산할 때로 구분해서 해석하면 된다.
C++ 캐스팅 이젠 좀 이해가 되시는지?
-
사실 C-style cast는 일련의 C++ 캐스팅을 순서대로 (컴파일 시점에서) 시도해보는 것 뿐이다. 여기서는 그 순서에 따라
static_cast
가 선택된다. 순서 자체는 stackoverflow.com의 이 답변을 참고하자. 덤으로 저 글에는 C++ 캐스팅 자체의 용법도 잘 설명되어 있다. ↩︎ -
두번째 값은 실행하는 플랫폼/C-runtime에 따라 다르다. ↩︎
-
alignment에 따라 바뀔 순 있다. ↩︎