가상 메서드 테이블

Virtual method table

Virtual Method Table(VMT; 가상 방식 테이블), Virtual Function Table, Virtual Call Table, Dispatch Table, vtable 또는 vftable은 동적 디스패치(또는 런타임 방식 바인딩)를 지원하기 위해 프로그래밍 언어로 사용되는 메커니즘입니다.

클래스가 가상 함수(또는 메서드)를 정의할 때마다 대부분의 컴파일러는 가상 메서드 테이블이라고 불리는 (가상) 함수에 대한 포인터 배열을 가리키는 숨겨진 멤버 변수를 클래스에 추가합니다.이러한 포인터는 실행 시 적절한 함수 구현을 호출하기 위해 사용됩니다. 컴파일 시에는 기본 함수를 호출할지 또는 기본 클래스에서 상속하는 클래스에 의해 구현될지 아직 알 수 없기 때문입니다.

이러한 동적인 디스패치를 실장하는 방법은 여러 가지가 있습니다만, 특히 가상 메서드테이블의 사용은 C++ 및 관련 언어(D 및 C# )에서 자주 사용됩니다.Visual Basic 및 Delphi와 같이 객체의 프로그래밍 인터페이스를 구현에서 분리하는 언어도 이 방법을 사용하는 경향이 있습니다. 왜냐하면 Visual Basic은 객체가 다른 메서드 포인터 세트를 사용하는 것만으로 다른 구현을 사용할 수 있기 때문입니다.

프로그램에 상속 계층에 슈퍼 클래스, 슈퍼 클래스,Cat, 및 2개의 서브클래스,HouseCat그리고.Lion.학급Cat라고 하는 이름의 가상 함수를 정의합니다.speak그 서브클래스는 적절한 구현을 제공할 수 있습니다(예를 들어 다음 중 하나).meow또는roar) 프로그램이 를 호출할 때speak에서 기능하다Catreference (인스턴스를 참조할 수 있습니다.Cat또는 의 인스턴스HouseCat또는Lion)는 을 디스패치하는 기능의 실장을 코드로 결정할 수 있어야 합니다.이는 객체에 대한 참조 클래스가 아니라 객체의 실제 클래스에 따라 달라집니다().Cat클래스는 일반적으로 정적( 컴파일 시)으로 판별할 수 없기 때문에 컴파일러도 그 시점에서 호출할 함수를 결정할 수 없습니다.대신 콜을 적절한 기능으로 동적으로(, 실행 시) 디스패치해야 합니다.

실행

개체의 가상 메서드 테이블에는 개체의 동적으로 바인딩된 메서드의 주소가 포함됩니다.메서드 호출은 객체의 가상 메서드 테이블에서 메서드 주소를 가져와 수행됩니다.가상 메서드 테이블은 같은 클래스에 속하는 모든 객체에 대해 동일하므로 일반적으로 이들 간에 공유됩니다.유형 호환 클래스에 속하는 개체(예를 들어 상속 계층의 형제)에는 동일한 레이아웃을 가진 가상 메서드 테이블이 있습니다. 지정된 메서드의 주소는 모든 유형 호환 클래스에 대해 동일한 오프셋으로 표시됩니다.따라서 주어진 오프셋에서 가상 메서드 테이블로 메서드 주소를 가져오면 객체의 실제 [1]클래스에 해당하는 메서드가 생성됩니다.

C++ 규격에서는 다이내믹 디스패치의 실장 방법에 대해서는 정확하게 규정되어 있지 않지만 컴파일러는 일반적으로 같은 기본 모델에서 약간의 변형을 사용합니다.

일반적으로 컴파일러는 클래스별로 별도의 가상 메서드 테이블을 만듭니다.오브젝트가 생성되면 이 테이블에 대한 포인터(가상 테이블포인터 또는 VPTR)가 이 오브젝트의 숨겨진 멤버로 추가됩니다.이와 같이 컴파일러는 클래스의 가상 메서드 테이블의 주소로 새로운 객체의 가상 테이블 포인터를 초기화하기 위해 각 클래스의 생성자에 "숨겨진" 코드를 생성해야 합니다.

많은 컴파일러가 가상 테이블 포인터를 오브젝트의 마지막 멤버로 배치합니다.다른 컴파일러는 가상 테이블 포인터를 첫 번째 멤버로 배치합니다.포터블 소스 코드는 어느 [2]쪽이든 동작합니다.를 들어 g++는 포인터를 오브젝트 [3]끝에 배치했습니다.

C++ 구문의 다음 클래스 선언을 검토합니다.

학급 지하 1층 { 일반의:   가상 ~지하 1층() {}   무효 가상이 아닌() {}   가상 무효 f1() {}   인트 int_in_b1; };  학급 B2 { 일반의:   가상 ~B2() {}   가상 무효 f2() {}   인트 int_in_b2; }; 

다음 클래스를 도출하는 데 사용됩니다.

학급 D : 일반의 지하 1층, 일반의 B2 { 일반의:   무효 d() {}   무효 f2() 덮어쓰다 {}   인트 int_in_d; }; 

및 다음 C++ 코드 조각:

B2 *b2 = 신규 B2(); D  *d  = 신규 D(); 

GCC의 g++ 3.4.6은 오브젝트의 다음 32비트 메모리 레이아웃을 생성합니다.b2다음과 같습니다.[nb 1]

B2: +0: B2 +4의 가상 메서드테이블 포인터: B2의 int_in_b2 가상 메서드테이블 값: +0: B2::f2()

및 오브젝트에 대한 다음 메모리 레이아웃d:

d: +0: D(B1의 경우)의 가상 메서드테이블 포인터 +4: D(B2의 경우)의 가상 메서드테이블 포인터 +12: int_in_b2 +16: int_in_d의 합계 사이즈: 20 바이트.D의 가상 방식 테이블(B1의 경우): +0:B1:f1() // B1:f1()은 D의 가상 방식 테이블(B2의 경우)보다 우선되지 않습니다.+0:D:f2() // B2:f2()는 D:f2()에 의해 재정의됩니다.가상 방식에서는 B2:f2의 위치가 비활성화됩니다.

이러한 함수는 키워드를 전송하지 않습니다.virtual(예:fnonvirtual()그리고.d())는 일반적으로 가상 방식 테이블에 표시되지 않습니다.기본 생성자에 의해 제시된 특수한 경우에는 예외가 있습니다.

기본 클래스의 가상 소멸자도 기록해 두십시오.B1그리고.B2이 두 가지 요소는 다음 사항을 확실히 하기 위해 필요합니다.delete d뿐만 아니라 기억을 해방시킬 수 있다D, 단,B1그리고.B2,한다면d다음 유형에 대한 포인터 또는 참조입니다.B1또는B2예를 단순화하기 위해 메모리 레이아웃에서 제외되었습니다.[nb 2]

메서드의 덮어쓰기f2()수업 중에D가상 메서드 테이블을 복제하여 구현합니다.B2포인터를 교체합니다.B2::f2()에 대한 포인터로D::f2().

다중 상속 및 트렁크

g++ 컴파일러는 클래스의 다중 상속을 구현합니다.B1그리고.B2수업 중에D기본 클래스마다 하나씩 두 개의 가상 메서드 테이블을 사용합니다.(복수의 상속을 실장하는 다른 방법이 있습니다만, 이것이 가장 일반적인 방법입니다).이 때문에 캐스팅 시 '쿵크'라고도 불리는 '포인트 수정'이 필요합니다.

다음 C++ 코드를 고려합니다.

D  *d  = 신규 D(); 지하 1층 *b1 = d; B2 *b2 = d; 

하는 동안에d그리고.b1이 코드를 실행한 후 동일한 메모리 위치를 가리킵니다.b2로케이션을 가리킵니다.d+8(메모리 위치를 8바이트 초과)d)이렇게 해서b2내부를 가리키다d예를 들어 '마음에 든다'는 것B2예를 들어, 메모리 레이아웃은 의 인스턴스와 동일합니다.B2.

호출

에의 문의d->f1()참조에 의해 처리됩니다.dD::B1vpointer, 검색f1가상 메서드 테이블에 입력한 후 해당 포인터를 참조 해제하여 코드를 호출합니다.

단일 상속

단일 상속(또는 단일 상속만 있는 언어)의 경우 vpointer가 항상 첫 번째 요소인 경우d(많은 컴파일러와 마찬가지로) 이것은 다음과 같은 의사 C++로 감소합니다.

(*((*d)[0]))(d) 

어디에*d가상 메서드 테이블을 참조합니다.D그리고.[0]는 가상 방식 테이블의 첫 번째 방식을 나타냅니다.파라미터d오브젝트에 대한 this포인터가 됩니다.

다중 상속

보다 일반적인 경우, 콜링(calling)B1::f1()또는D::f2()더 복잡합니다.

(*(*(d[+0]/*D의 가상 메서드 테이블 포인터(B1)*/)[0]))(d)   /* 콜 d-> f1() */ (*(*(d[+8]/*D의 가상 메서드 테이블 포인터(B2)*/)[0]))(d+8) /* 콜 d->f2() */ 

문의처d->f1()a를 통과하다B1pointer를 파라미터로 지정합니다.문의처d->f2()a를 통과하다B2pointer를 파라미터로 지정합니다.이 두 번째 콜에서는 올바른 포인터를 생성하기 위해 수정이 필요합니다.의 장소B2::f2에 대한 가상 메서드 테이블에 없습니다.D.

그에 비해 콜은d->fnonvirtual()훨씬 심플합니다.

(*지하 1층::가상이 아닌)(d) 

효율성.

가상 콜은 컴파일된 포인터에 대한 단순한 점프인 비가상 콜에 비해 적어도 인덱스된 참조 해제와 경우에 따라서는 "픽스업" 추가를 필요로 합니다.따라서 가상 함수를 호출하는 것은 본질적으로 비가상 함수를 호출하는 것보다 느립니다.1996년에 실시된 실험에 따르면 실행 시간의 약 6-13%가 단순히 올바른 기능에 디스패치하는 데 소비되지만 오버헤드는 50%에 [4]달할 수 있습니다.가상 기능의 비용은 캐시가 훨씬 더 크고 분기 예측이 더 우수하기 때문에 최신 아키텍처에서는 그리 높지 않을 수 있습니다.

게다가 JIT 컴파일을 사용하지 않는 환경에서는, 통상, 가상 함수 콜을 인라인 할 수 없습니다.어떤 경우에는 컴파일러가 예를 들어 룩업과 간접 호출이 각 인라인 본문의 조건부 실행으로 대체되는 반가상화라고 알려진 프로세스를 수행할 수 있지만 이러한 최적화는 일반적이지 않습니다.

이러한 오버헤드를 피하기 위해 컴파일러는 보통 콜이 컴파일 시 해결될 때마다 가상 메서드테이블을 사용하지 않습니다.

그 때문에, 에의 콜은f1위의 경우 컴파일러가 다음과 같은 것을 알 수 있기 때문에 테이블 룩업이 필요하지 않을 수 있습니다.d지탱할 수 있는 것은 밖에 없다D이 시점에서D덮어쓰지 않음f1컴파일러(또는 옵티마이저)는, 다음의 서브 클래스가 없는 것을 검출할 수 있습니다.B1우선하는 프로그램 내의 어느 곳에서도f1에의 문의B1::f1또는B2::f2구현이 명시적으로 지정되어 있기 때문에 테이블룩업이 필요하지 않을 수 있습니다(단, 'this'-fixup은 여전히 필요).

대체 제품과의 비교

가상 방식 표는 일반적으로 동적 디스패치를 실현하기 위한 뛰어난 퍼포먼스 트레이드오프이지만 바이너리 트리 디스패치 등 퍼포먼스는 높지만 [5]비용은 다릅니다.

단, 가상 메서드테이블에서는 디스패치 시 모든 파라미터의 유형을 고려할 수 있는 복수의 디스패치(CLOC, Dylan 또는 Julia )와는 달리 특별한 "this" 파라미터에 대해서만1개의 디스패치를 허용합니다.

또한 가상 메서드 테이블은 디스패치가 알려진 메서드 세트로 제한되어 있는 경우에만 작동하기 때문에 duck 타이핑 언어(Smalltalk, Python 또는 JavaScript )와 달리 컴파일 시 구축된 단순한 배열에 배치할 수 있습니다.

이러한 기능 중 하나 또는 양쪽을 제공하는 언어에서는 종종 해시 테이블 또는 이와 동등한 방법으로 문자열을 검색하여 디스패치합니다.이를 보다 빠르게 하기 위한 다양한 기술(예: 메서드 이름 삽입/토큰화, 캐싱 검색, 저스트타임 컴파일)이 있습니다.

「 」를 참조해 주세요.

메모들

  1. ^ G++의-fdump-class-hierarchy(버전 8 이후:-fdump-lang-class) 인수를 사용하여 수동으로 검사하기 위해 가상 메서드테이블을 덤프할 수 있습니다.AIX VisualAge XlC 컴파일러의 경우,-qdump_class_hierarchy클래스 계층 및 가상 함수 테이블 레이아웃을 덤프합니다.
  2. ^ "C++ - why there are two virtual destructor in the virtual table and where is address of the non-virtual function (gcc4.6.3)".

레퍼런스

  1. ^ 엘리스 & 스트루스트럽 1990, 227-232페이지
  2. ^ Danny Kalev. "C++ 레퍼런스 가이드: 오브젝트 모델 II." 2003."상속과 다형성"과 "다중상속"이라는 제목.
  3. ^ "C++ ABI Closed Issues". Archived from the original on 25 July 2011. Retrieved 17 June 2011.{{cite web}}: CS1 maint: bot: 원래 URL 상태를 알 수 없습니다(링크).
  4. ^ Drisen, Karel 및 Hölzle, Urs, "가상 함수 호출의 직접 비용(C++)", OPSLA 1996
  5. ^ Zendra, Olivier 및 Drisen, Karel, "Java에서의 동적 디스패치를 위한 스트레스 테스트 제어 구조", 페이지 105-118, 제2회 Java 가상 머신 연구 및 기술 심포지엄, 2002(JVM '02)