오늘의 코드카타
미로 탈출 명령어
- 이동 경로 + 그리디
- LV3 - 37%
-> 문제 풀이
오늘의 공부
진행한 과제
오늘의 CS
vtable이란?
- C++에서 가상 함수를 지원하기 위해 컴파일러가 내부적으로 사용하는 함수 포인터 테이블
- OOP의 특성인 다형성을 제공해줌
동작과정
- 함수 내부에
virtual 키워드를 가진 가상 함수가 존재하는지 확인
- 가상 함수가 존재한다며 컴파일러는 클래스마다 vtable을 하나씩 만듦
- 객체마다 vptr(가상 테이블 포인터)를 멤버로 추가
-> vptr은 해당 객체의 vtable을 가리킴
- 해당 함수를 호출하게 되면 vptr을 통해 vtable을 확인한 후, 해당 주소의 함수를 실행
예시
struct A {
virtual void Foo() { std::cout << "A::Foo\n"; }
virtual void Bar() { std::cout << "A::Bar\n"; }
};
struct B : public A {
void Foo() override { std::cout << "B::Foo\n"; }
// Bar는 override 안 함
};
A의 vtable
[0] → &A::Foo
[1] → &A::Bar
B의 vtable
[0] → &B::Foo // 오버라이딩됨
[1] → &A::Bar // 오버라이딩 안 됨 → 부모 함수 그대로 사용
단점
- 함수 호출 시, 간접 참조 1번 (vptr -> vtable -> 함수) 오버헤드
- 메모리 사용량 증가 (객체마다 vptr 필요, 클래스마다 vtable 필요)
순수 가상 함수를 만들면 vtable 내부 구조
- 순수 가상 함수의 경우, vtable에는 단순히 추상 함수 자리로 채워짐
- 그 클래스가 추상 클래스라면 해당 함수 슬롯을 직접 호출 불가하게 함
- gcc/Clang은 null을 표현해주는 특수 슬롯을 넣음
- MSVC는
__purecall이라는 런타임 핸들러 함수 포인터를 넣음
- MSVC는
__purecall 핸들러를 넣은 이유
- 그냥 segfault 같은 호출보다는 구체적인 진단 메세지를 주기 위해 설계
- 디버깅 친화적인 설계를 위해서
- visual studio 2022에서 테스트 해본 결과, 실제 메모리 주소에 purecall 핸들러가 들어가있음


- 테스트 코드
#include <iostream>
#include <cstdint>
#include <iomanip>
using namespace std;
extern "C" int __cdecl _purecall(void); // CRT의 실제 시그니처와 맞춤
struct Base {
virtual void Foo() = 0;
virtual void Bar() { std::cout << "Base::Bar\n"; }
};
struct Derived : public Base {
void Foo() override { std::cout << "Derived::Foo\n"; }
void Bar() override { std::cout << "Derived::Bar\n"; }
};
int main() {
Derived d;
Base* b = &d;
// 함수 포인터로 취득
auto fptr = reinterpret_cast<void(*)()>(&_purecall);
// 함수 포인터 → 정수로 변환(주소 출력)
std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(fptr);
std::cout << "_purecall addr = 0x"
<< std::hex << std::setw(sizeof(void*) * 2) << std::setfill('0')
<< addr << std::dec << std::endl;
b->Foo();
b->Bar();
return 0;
}
순수 가상 함수를 구현하지 않았을 때 발생하는 오류
1. 인스턴스화하지 않는 경우
2. 자손 클래스에서 선언하지 않은 경우
struct Base {
virtual void Foo() = 0; // 순수 가상 함수
};
struct Derived : public Base {
};
int main() {
Derived d;
}
- MSVC : 추상 클래스를 인스턴스화할 수 없다는 컴파일 에러 발생

- gcc compiler, Clang : 추상 클래스를 인스턴스화할 수 없다는 컴파일 에러 발생

3. 자손 클래스에서 선언만 하고, 정의하지 않은 경우
struct Base {
virtual void Foo() = 0; // 순수 가상 함수
};
struct Derived : public Base {
virtual void Foo() override;
};
int main() {
Derived d;
}
- MSVC : 정의되지 않은 함수 포인터를 불렀기 때문에 링크 에러 발생

- gcc compiler, Clang : 정의되지 않은 함수 포인터를 불렀기 때문에 링크 에러 발생

4. 자손 클래스에서 구현했지만, 추상 클래스 생성자/소멸자에서 직접 호출
struct Base {
Base() { Foo(); }
virtual void Foo() = 0; // 순수 가상 함수
};
struct Derived : public Base {
virtual void Foo() override { cout << "Derived::Foo\n"; }
};
int main() {
Derived d;
d.Foo();
}
- MSVC : 정의되지 않은 함수 포인터를 불렀기 때문에 링크 에러 발생

- gcc compiler, Clang : pure virtual function call 경고 + 정의되지 않은 함수 포인터를 불렀기 때문에 링크 에러 발생

5. 자손 클래스에서 구현했지만, 추상 클래스 생성자/소멸자에서 다른 함수에서 호출
struct Base {
Base() { Init(); }
void Init() { Foo(); }
virtual void Foo() = 0; // 순수 가상 함수
};
struct Derived : public Base {
virtual void Foo() override { cout << "Derived::Foo\n"; }
};
int main() {
Derived d;
d.Foo();
}
- MSVC :
abort() 런타임 에러 발생

- gcc compiler, Clang : pure virtual method called 런타임 에러 발생
