C++ 가상 함수의 동작 원리

프로그래밍/C++ 2019.10.22 댓글 Plorence

C++는 가상 함수들이 어떻게 동작해야 하는지 규정하고 있고 그 구현은 컴파일러 개발자의 몫입니다.

구현된 방법을 알면 가상 함수의 개념을 좀 더 잘 이해할 수 있습니다.

 

컴파일러는 가상 함수를 어떻게 처리하는가?

먼저 각각의 객체에 숨겨진 멤버하나씩 추가합니다.

숨겨진 멤버는 함수의 주소들로 이루어진 배열을 지시하는 포인터를 저장합니다.

일반적으로 그 배열을 가상 함수 테이블(VirTual Function Table, 줄여서 vtbl)이라고 합니다.

 

vtbl에는 클래스의 객체들을 위해 선언된 가상 함수들의 주소가 저장되어 있습니다.

기초 클래스의 한 객체는 그 클래스를 위한 모든 가상 함수들의 주소로 이루어진 테이블을 지시하는 포인터를 가집니다.

파생 클래스도 가상 함수들의 주소로 이루어진 별개의 테이블을 지시하는 포인터를 가집니다.

만약 가상으로 선언하고 파생 클래스에서 재정의를 하지 않았다면 기초 클래스에 있는 함수의 주소를 저장합니다.

 

가상 함수를 호출하면 프로그램은 객체에 vtbl주소가 저장되어 있다는 것을 알게 되고 함수 주소들로 이루어진 테이블에 접근합니다.

사용하는 함수가 클래스 선언에 정의된 첫 번째 가상 함수라면 프로그램은 배열에 있는 첫 번째 주소를 사용합니다.

class Person {
public:
        virtual void Move();
        virtual void Show();
};
class Man : public Person {
public:
        virtual void PowerUp();
        void Show();
        
};

이러한 클래스가 정의되어 있다면 아래의 그림처럼 됩니다.

그림으로 표현

만약에 Man 클래스의 Show함수호출되면 아래와 같이 동작합니다.

  1. Man->virtual_ptr의 값을 얻는다. (6655)

  2. 6655 테이블로 간다.

  3. 그 테이블에 있는 두 번째 함수의 주소를 얻는다. (5678) (덧붙여서 이 테이블의 자료구조는 hashtable이라고 들었습니다.)

  4. 해당 주소로 가서 함수를 실행한다.

다른 함수도 값만 다를 뿐 같은 방식으로 동작하게 됩니다.

 

위와 같은 동작 때문에 아래와 같은 부담이 생깁니다.

  • 각 객체의 크기가 주소 하나를 저장하는데 필요한 양만큼 커집니다.

  • 각각의 클래스에 대해 컴파일러는 가상 함수들의 주소로 이루어진 하나의 테이블(배열)을 만듭니다.

  • 각각의 함수 호출에 대해, 실행할 함수의 주소를 얻기 위해 테이블에 접근하는 가외의 단계가 더 필요합니다.

 

댓글