← 문제로

31. 다중 상속의 객체 레이아웃과 this 포인터 조정(pointer adjustment)

난이도 최상
내 리뷰 · C++
해설 · C++

해설 — 다중 상속의 객체 레이아웃과 this 포인터 조정(pointer adjustment)

난이도: 최상

예측 출력

1
0
16
C::fb

(A* 비교 = 1, B* 비교 = 0, B 서브오브젝트 오프셋 = 16, reinterpret_cast 비교 = 0, 그리고 C::fb)

전체 출력(줄 단위):

1
0
16
0
C::fb

핵심 개념

다중 상속 C : A, B 에서 C 객체는 메모리에 A 서브오브젝트 다음에 B 서브오브젝트가 이어 붙는 형태로 배치된다. 각 다형적 기반(A, B)은 자신의 vptr 를 가진다(Itanium C++ ABI, 64비트 기준).

C 객체 레이아웃(대략, 64비트):
  offset  0 : [A vptr]        (8)
  offset  8 : A::a            (4) + padding(4)
  offset 16 : [B vptr]        (8)   <- B 서브오브젝트 시작
  offset 24 : B::b            (4)
  offset 28 : C::c            (4)
  sizeof(C) = 32

단계별 내부 동작 (출력 근거)

  1. A* pa = pc; — A 서브오브젝트는 오프셋 0 에 있으므로 주소 조정이 없다. pa == pc (as void*) → 1.
  2. B* pb = pc; — B 서브오브젝트는 오프셋 16 에 있으므로, 컴파일러가 업캐스트 시 this+16 을 더해 B 서브오브젝트를 가리키게 조정한다. 따라서 pb != pc (as void*) → 0.
  3. (char*)pb - (char*)pc = 16. B 서브오브젝트의 오프셋. (플랫폼/ABI에 따라 다를 수 있으나 이 환경은 16.)
  4. reinterpret_cast<B*>(pc) — reinterpret_cast 는 비트 재해석일 뿐 조정하지 않는다. 그래서 pbBad 는 C 시작 주소(=pc)를 그대로 B* 로 본다. 이는 잘못된 B 포인터다. pbBad == pb? pbBad 는 pc 주소, pb 는 pc+16 → 다르다 → 0. (pbBad 로 B 멤버 접근 시 A 영역을 B로 오해해 UB.)
  5. pb->fb(); — pb 는 B 서브오브젝트를 가리키고, B 의 vtable 슬롯에는 C::fb 로 가는 thunk가 들어있다. thunk 는 this 를 다시 -16 조정해 C* 로 만든 뒤 C::fb 를 호출한다. 출력 C::fb.

왜 thunk 가 필요한가

C::fb 의 본문은 this 가 완전한 C 객체를 가리킨다고 가정한다(멤버 c 접근 등). 그런데 pb->fb() 는 B 서브오브젝트 주소(pc+16)로 가상 디스패치한다. 그래서 B 의 vtable 엔트리는 곧장 C::fb 가 아니라, this -= 16C::fb 로 점프하는 작은 adjustor thunk 를 가리킨다. 이 조정으로 오버라이더가 올바른 C* 를 받는다. A 쪽 vtable 의 fa 는 A 가 오프셋 0 이라 조정이 0 이라 thunk 가 없을 수 있다.

흔한 오해·함정

  • static_cast vs reinterpret_cast: 관련 타입 간 업/다운 캐스트는 반드시 static_cast(또는 암시적)를 써야 포인터 조정이 일어난다. reinterpret_cast 는 조정하지 않아 서브오브젝트 포인터가 어긋난다.
  • void* 왕복의 위험: C*void* 로 지웠다가 B* 로 되살리면 조정 정보가 사라져 깨진다. 다형 객체를 void* 로 넘길 땐 가장 파생 타입 포인터를 저장하거나 dynamic_cast<void*>(가장 파생 주소 획득)를 활용한다.
  • C-스타일 캐스트의 위험: 상황에 따라 static_cast 또는 reinterpret_cast 로 해석되어 예측이 어렵다.
  • 단일 상속에서는 기반이 항상 오프셋 0 이라 조정이 없어 이런 문제가 드러나지 않는다 — 다중 상속에서만 두드러진다.

면접 포인트

  • 다중 상속 객체의 메모리 배치와 각 기반의 vptr, 서브오브젝트 오프셋.
  • 업캐스트 시 this 조정이 필요한 이유와, static_cast(조정 O)/reinterpret_cast(조정 X)의 차이.
  • adjustor thunk 가 가상 디스패치에서 하는 역할(this 되돌림).