가상함수테이블과 가상함수테이블포인터
출처: http://tcpschool.com/c/c_memory_structure
-가상함수 테이블은 코드영역에 선언되며 테이블안에는 virtual 함수를 가리키는 주소가 들어있다.( 멤버 함수는 모두 code 영역에 저장되며 멤버 함수는 객체마다 다르게 동작하는 것이 아니기 때문에 객체마다 함수가 할당되면 비효율적일 것이다. 따라서 해당 클래스의 모든 객체는 code 영역에 있는 멤버 함수를 공유하며 사용한다.
-참고로 비정적 멤버 변수는 객체의 생성과 동시에 생성된다. 객체 내의 지역 변수와 동일하므로 스택에 저장된다. (물론 동적으로 할당되는 메모리는 heap 영역에 저장됨) 그리고 만약 클래스 내에 정적 멤버 변수를 선언했다면 이 변수는 객체 생성 시 할당되는 것이 아니라 프로그램 시작 시 데이터 영역에 생성된다. 따라서 정적 멤버 변수는 객체의 크기에 영향을 미치지 않는다. )
-가상함수 테이블은 컴파일타임에 채워져서 런타임에 사용된다.
-가상함수테이블포인터를 모든 객체가 각각 갖고있고
가리키는 클래스의 가상함수테이블은 한 클래스끼리 공유한다.
(가상 함수 테이블은 컴파일 시 각 클래스당 1개만 생성되고 같은 클래스 객체들은 모두 같은 가상 함수 테이블을 공유한다.
- 가상 함수를 선언해도 override 되지 않으면, 가상 함수 테이블에 추가되지 않는다.
원리
Animal* a = new Dog();
이럴시에 a는 Animal*타입이지만 Animal클래스가 가상함수를 포함하고 있으며
Dog클래스로 동적할당을 받았기에 생성될때 생성자에서 a객체의 가상함수테이블포인터가
Dog클래스의 가상함수테이블을 가리키게 된다. (Dog클래스가 컴파일 타임에 가상함수테이블을 작성한다)
일반적으론 가상함수가 일반함수에 비해 성능이 많이 뒤쳐지지는 않으나 아래 상황에선 문제가 될 수 있다.
- 가상함수에선 inline을 쓸 수 없다. 매크로 함수와 비슷하게 lnline 함수는 컴파일 과정에서 코드를 대체하겠다라는 의미이다.
그런데 virtual 함수는 컴파일 과정에서는 어떤값이 쓰일지 알 수 없다.
{
Animal*a = new Dog()가 될지 Animal*a = new Cat()이 되어서 a.Walk()가 Dog의 Walk일지 Cat의 Walk일지 모른다.
}
동적으로 객체가 변할 수 있기 때문에 그 때마다 객체의 vptr이 가리키는 vtbl이 다를 수 있다.
즉, 컴파일 단계에서는 가상 함수에 대해서 inline을 할 수 없다.
- 다중 상속을 하게 되면, 다중 상속을 받은 클래스의 객체 안에 여러개의 vptr이 존재하게 된다. 따라서 vptr을 선택하는데에도 오버헤드가 발생하는데에도 비용이 증가하게 된다.
-
*RTTI (RunTime Type Identification, 런타임 타입 식별)
실행중에 객체와 클래스 정보를 알아내기 위해 사용된다. 쓸만한 기능이지만 이를 사용하기 위해서는 역시 객체에 어딘가에는 해당 내용이 저장되어 있어야한다. 모든 객체마다 갖고 있을 필요는 없고, vptr을 이용해서 찾는 vtbl의 첫번째 요소에 이런 type_info를 참조할 수 있는 주소를 저장하고 있게 된다. 이는 typeid 연산자를 통해서 해당 값에 접근할 수 있다. 따라서 typeid를 사용하게 되면, type_info의 객체 메모리에 추가로 vtbl의 저장공간이 추가로 소모된다.
하지만 RTTI를 통해서 type_info를 뽑아 내기 위해서는 한 가지 조건이 있다. 바로 클래스가 하나 이상의 가상 함수를 갖어야한다는 것이다. 위 설명을 들으면 당연한 것이다. typeid는 vptr을 타고 vtbl의 첫번째 요소에서 값을 찾는다고 했다. 그런데 vtbl이 있으려면 class에 하나 이상의 가상함수가 있어야 만들어지는 것이다. vptr도 마찬가지이다.
=>Dynamic_Cast와도 관련
'[C++]' 카테고리의 다른 글
[C++ 20] Coroutine (0) | 2024.12.11 |
---|---|
[C++ 20] Range (0) | 2024.12.09 |
[C++ 20] Module (1) | 2024.11.13 |
Modern C++ #9 전달 참조(forwarding reference) (0) | 2024.10.22 |
Modern C++ #8 오른값 참조(rvalue reference)와 std::move (0) | 2024.10.22 |