2015. 11. 13. 00:44

Policies/Binary Compatibility Examples - 이진 호환성 예제

Policies/Binary Compatibility Examples

이진 호환성 예제들


This page is meant as examples of things you cannot do in C++ when maintaining binary compatibility.

이 페이지는 이진 호환성을 유지할 때 C++ 에서 할 수 없는 것들에 대한 예제들을 설명합니다.

Unexport or remove a class 

클래스 내보내기 취소 또는 제거

BeforeAfter
class KDECORE_EXPORT KUrl
{
   // [...]
};
class KUrl
{
   // [...]
};

Reason: the symbols for the class above are not added to the exported symbols list of the library, so other libraries and applications cannot see them.

이유: 위의 클래스들에 대한 심볼들은 라이브러리의 내보내진 심볼들 목록에 추가가 되지 않으므로, 다른 라이브러리나 응용 프로그램에서 이들을 볼수 없습니다.

Change the class hierarchy

클래스의 구조를 변경
BeforeAfter
class MyClass: public BaseClass
{
   // [...]
};
class MyClass: public BaseClass, public OtherBaseClass
{
   // [...]
};
class MyClass: public BaseClass1, public BaseClass2
{
   // [...]
};
class MyClass: public BaseClass2, public BaseClass1
{
   // [...]
};

Reason: the size and/or order of member data in the class changes, causing existing code to allocate too much or too little memory, read/write data at the wrong offsets.

이유 : 클래스 내의 맴버 자료들의 순서/크기를 변경하는 것은 기존의 코드에게 메모리를 많이 또는 적게 할당하게 해서, 잘못된 주소 오프셋으로 데이터를 읽고/쓰게 할 것입니다.

Change the template arguments of a template class

템플릿 클래스의 템플릿 인수를 변경

BeforeAfter
template<typename T1>
class MyTemplateClass
{
    // [...]
};
template<typename T1, typename T2 = void>
class MyTemplateClass
{
    // [...]
};

// GCC mangling before: _Z3foo15MyTemplateClassIiE
//              after:  _Z3foo15MyTemplateClassIivE
void foo(MyTemplateClass<int>);

Reason: the mangling of the functions related to this template type change because its template expansion changes too. This can happen both for member functions (for example, the constructor) as well as functions that take it as a parameter.

이유 : 이 템플릿 자료형과 관련된 함수의 맹글링(이름 변경) 구조가 변경됩니다, 왜냐하면 그것의 템플릿 확장들도 변경되기 때문입니다. 이런 현상은 파라미터로 같는 함수나 맴버 함수들 (예를 들어 생성자) 에게도 나타납니다

Unexport a function

함수 내보내기 취소

BeforeAfter
Q_CORE_EXPORT const char *qVersion();
const char *qVersion();
namespace KSocketFactory {
    KDECORE_EXPORT QTcpSocket *connectToHost(...);
}
namespace KSocketFactory {
    QTcpSocket *connectToHost(...);
}

Reason: the symbols for the functions above are not added to the exported symbols list of the library, so other libraries and applications cannot see them.

이유: 위의 심볼들은 라이브러리의 내보내진 심볼들 목록에 추가가 되지 않으므로, 다른 라이브러리나 응용 프로그램에서 이들을 볼수 없습니다.

Inline a function

인라인 함수
BeforeAfter
int square(int n);
inline int square(int n) { return n * n; }
int square(int n) { return n * n; }
inline int square(int n) { return n * n; }
class Math
{
    int square(int n);
};
 
// the following could be in a .cpp
int Math::square(int n)
{ return n * n; }
class Math
{
    int square(int n);
};
 
// the following could be in a .cpp
inline int Math::square(int n)
{ return n * n; }
class Math
{
    int square(int n);
};
 
// the following could be in a .cpp
int Math::square(int n)
{ return n * n; }
class Math
{
    int square(int n)
    { return n * n; }
};

Reason: when a function is declared inline and the compiler does inline it at its call point, the compiler does not have to emit an out-of-line copy. Code that exists and was calling this function will therefore not be able to resolve the function anymore. Also, when compiling with GCC and -fvisibility-inlines-hidden, if the compiler does emit an out-of-line copy, it will be hidden (not added to the exported symbols table) and therefore not accessible from other libraries.

이유 : 인라인으로 선언되고, 컴파일러가 호출 지점에서 인라인 형태로 동작할 시킬때, 컴파일러는 out-of-line (클래스 밖에 정의된) 사본을 발산할 필요가 없습니다. 이 함수를 부르는 코드는 (인라인 이므로) 더 이상 그 함수를 정의 할 수 없습니다. 또한 GCC 을 -fvisibility-inlines-hidden 옵션을 써서 컴파일 할 때, 컴파일러는 out-of-line 사본을 발산하지 않는다면, 그 코드는 숨겨질 것입니다. (내보낸 심볼 테이블에 추가되지 않음), 그러므로 다른 라이브러리에서 이를 접근할 수 없습니다.

Change the parameters of a function

함수의 파라미터들을 변경
BeforeAfter
// GCC mangling: _Z11doSomethingii
// MSVC mangling: ?doSomething@@YAXHH@Z
void doSomething(int i1, int i2);
// GCC mangling: _Z11doSomethingis
// MSVC mangling: ?doSomething@@YAXHF@Z
void doSomething(int i1, short i2);
// GCC mangling: _Z11doSomethingii
// MSVC mangling: ?doSomething@@YAXHH@Z
void doSomething(int i1, int i2);
// GCC mangling: _Z11doSomethingiii
// MSVC mangling: ?doSomething@@YAXHHH@Z
void doSomething(int i1, int i2, int i3 = 0);
// GCC mangling: _Z11doSomethingRi
// MSVC mangling: ?doSomething@@YAXABH@Z
void doSomething(int &i1);
// GCC mangling: _Z11doSomethingRKi
// MSVC mangling: ?doSomething@@YAXAAH@Z
void doSomething(const int &i1);
void doSomething(int i1);
void doSomething(const int i1); // breaks with Sun CC
// GCC mangling: _Z11doSomethingPc
// MSVC mangling: ?doSomething@@YAXPAD@Z (32-bit)
void doSomething(char *ptr);
// GCC mangling: _Z11doSomethingPKc
// MSVC mangling: ?doSomething@@YAXPBD@Z (32-bit)
void doSomething(const char *ptr);

Reason: changing the parameters of a function (adding new or changing existing) changes the mangled name of that function. The reason for that is that the C++ language allows overloading of functions with the same name but slightly different parameters.

이유 : 함수의 파라미터를 변경하는 것 (새것을 추가하거나 기존의 것을 변경하는 것) 으로 맹글링된 함수의 이름이 변경됩니다. 이러한 이유는 이름은 같지만 조금이라도 다른 파라미터를 쓰는 함수에 대한 오버로딩을 허용하기 위함입니다.

I don't have the mangled name for the Sun CC example above, that compiler does enforce the constness of POD types in both declaration and implementation.

위 맹글링된 이름에 대한 예제에 대해, 선언과 구현 모두 POD (Plain Old Data, 생성자/파괴자/가상함수가 존재하지 않는 struct 구조를 가진 단순한 class 형태) 형식의 상수성을 강조하는 썬 CC (컴파일러) 예제는 가지고 있지 않습니다.

Change the return type

반환하는 리턴 값을 변경하기

BeforeAfter
// GCC mangling: _Z8positionv
// MSVC mangling: ?position@@YA_JXZ
qint64 position();
// GCC mangling: _Z8positionv
// MSVC mangling: ?position@@YAHXZ
int position();
// GCC mangling: _Z4namev
// MSVC mangling: ?position@@YAVQByteArray@@DXZ
QByteArray name();
// GCC mangling: _Z4namev
// MSVC mangling: ?position@@YAVQString@@XZ
QString name();
// GCC mangling: _Z4namev
// MSVC mangling: ?position@@YAPBDXZ
const char *name();
// GCC mangling: _Z4namev
// MSVC mangling: ?position@@YAVQString@@XZ
QString name();
// GCC mangling: _Z12createDevicev
// MSVC mangling: ?createDevice@@YAPAVQTcpSocket@@XZ
QTcpSocket *createDevice();
// GCC mangling: _Z12createDevicev (unchanged)
// MSVC mangling: ?createDevice@@YAPAVQIODevice@@XZ
QIODevice *createDevice();
// GCC mangling: _ZNK10QByteArray2atEi
// MSVC mangling: ?at@QByteArray@@QBA?BDH@Z
const char QByteArray::at(int) const;
// GCC mangling: _ZNK10QByteArray2atEi (unchanged)
// MSVC mangling: ?at@QByteArray@@QBADH@Z
char QByteArray::at(int) const;
// GCC mangling: _ZN6QEvent17registerEventTypeEi
// MSVC mangling: ?registerEventType@QEvent@@QAAXH@Z
int QEvent::registerEventType(int)
// GCC mangling: _ZN6QEvent17registerEventTypeEi (unchanged)
// MSVC mangling: ?registerEventType@QEvent@@QAAXW4Type@V0@@@Z
QEvent::Type QEvent::registerEventType(int)

Reason: changing the return type changes the mangled name of the function names in some compilers (GCC notably does not encode the return type). However, even if the mangling doesn't change, the convention on how the return types are handled may change.

이유 : 반환 값의 형태를 변경하는 것은 몇몇 컴파일러 (눈에 띄게도 GCC 컴파일러는 반환 형식을 부호화하여 이름에 넣지 않습니다.) 에서 맹글링 된 이름을 변경하게 합니다. 하지만, 맹글링 내용이 변경되지 않았다고 해서, 반환값의 상태에 따라 명명되어 지는 방법이 변경될 수 있습니다.

In the first example above, the return type changed from a 64- to a 32-bit integer, which means on some architectures, the upper half of the return register may contain garbage. In the second example, the return type changed from QByteArray to QString, which are two incompatible types.

첫 예제에 대해서, 64비트 에서 32비트로 반환값 형식이 변경되었는데, 이는 몇몇 아키텍쳐에서 상위 리턴 레지스터에 불필요한 값(가비지) 을 포함할 수 있다는 것을 의미합니다. 두번째 예제로 반환 값이 QByteArray 에서 QString 으로 변경되었는데, 이는 서로 호환되지 않는 형식입니다.

In the third example, the return type changed from a simple integer (a POD) to a QString -- in this case, the compiler usually needs to pass a hidden implicit first parameter, which won't be there. In this case, existing code calling the function will more than likely crash, due to trying to dereference the implicit QString* parameter that isn't there.

세번째 예제에서, 단순 정수형 (POD) 에서 QString 으로 반환 값이 변경되었습니다. 이 경우, 컴파일러는 존재하지 않을 첫번째 숨김 파라미터를 묵시적으로 전달할 필요성을 가집니다. 이 때, 이 코드에의 함수 호출은 QString* 파라미터가 존재하지 않는데에도 이를 역참조 시도를 하기 때문에 크래쉬가 발생할 수 있습니다.

In the fourth example, the return type changed from one POD type (an int) to another (an enum), which is also carried by an int. The calling sequence is most likely the same in all compilers, however the mangling of the symbol name changed, meaning that calls will fail due to an unresolved symbol.

네번째 예제로, 하나의 POD (정수형) 자료 형식에서 정수형을 실어 나르는 다른 자료형으로 반환 값 형식을 변경하였습니다. 이런 호출 순서는 모든 컴파일러에서 동일한 결과를 가지지만, 하지만 심볼 이름을 맹글링하는 과정에서 해결되지 않은 심볼로 인한 문제로 동작에 실패할 것입니다.

Change the access rights

접근 권한을 변경
BeforeAfter
class MyClass
{
protected:
    // GCC mangling: _ZN7MyClass11doSomethingEv
    // MSVC mangling: ?doSomething@MyClass@@IAAXXZ
    void doSomething();
};
class MyClass
{
public:
    // GCC mangling: _ZN7MyClass11doSomethingEv (unchanged)
    // MSVC mangling: ?doSomething@MyClass@@QAAXXZ
    void doSomething();
};

Reason: some compilers encode the protection type of a function in its mangled name.

이유 : 몇몇 컴파일러는 함수의 보호 형태를 맹글링된 이름에 부호로 넣습니다.

Change the CV-qualifiers of a member function

맴버 함수의 CV-수식자를 변경하기

BeforeAfter
class MyClass
{
public:
    // GCC mangling: _ZNK7MyClass9somethingEv
    // MSVC mangling: ?something@MyClass@QBAHXZ
    int something() const;
};
class MyClass
{
public:
    // GCC mangling: _ZN7MyClass9somethingEv
    // MSVC mangling: ?something@MyClass@QAAHXZ
    int something();
};

Reason: compilers encode the constness of a function in the mangled name. The reason they all do that is because the C++ standard allows overloading of functions that differ only by the constness.

이유 : 컴파일러는 맹글링된 함수의 이름에 상수성을 부호화하여 넣습니다. 모두 그렇게 하는 이유는 C++ 표준은 상수성이 다른 함수를 제외하고는 오버로딩을 허용하기 때문입니다.

Change the type of global data

전역 속성의 데이터의 형식을 변경

BeforeAfter
// GCC mangling: data (undecorated)
// MSVC mangling: ?data@@3HA
int data = 42;
// GCC mangling: data (undecorated)
// MSVC mangling: ?data@@3FA
short data = 42;
class MyClass
{
public:
    // GCC mangling: _ZN7MyClass4dataE
    // MSVC mangling: ?data@MyClass@@2HA
    static int data;
};
class MyClass
{
public:
    // GCC mangling: _ZN7MyClass4dataE
    // MSVC mangling: ?data@MyClass@@2FA
    static short data;
};

Reason: some compilers encode the type of the global data in its mangled name. Especially note that some compilers mangle even for simple data types that would be allowed in C, meaning the extern "C" qualifier makes a difference too.

이유 : 몇몇 컴파일러는 맹글링된 전역 변수 이름에 자료형을 부호화로 넣습니다. 특별하게도 C 에서 허용이 될 법한 단순한 자료형에 대해서도 컴파일러들은 맹글링을 작업을 하고 있음을 이를 통해 extern "C" 수식자를 써도 같은 차이점을 만들어 냄을 참고하시기 바랍니다.

Even if the mangling doesn't change, changing the type often changes the size of the data as well. That means code that was accessing the global data may be access too many or too few bytes.

맹글링이 변화가 없더라도, 자료형의 변경은 자료 크기 또한 변경 됨을 의미합니다. 이는 전역 변수의 자료 접근에 더 많거나 더 적은 수의 바이트 단위로 접근을 하게 될 수 있습니다.

Change the CV-qualifiers of global data

전역형 자료의 CV-수식자 변경
BeforeAfter
// MSVC mangling: ?data@@3HA
int data = 42;
// MSVC mangling: ?data@@3HB
const int data = 42;
class MyClass
{
public:
    // MSVC mangling: ?data@MyClass@@2HA
    static int data;
};
class MyClass
{
public:
    // MSVC mangling: ?data@MyClass@@2HB
    static const int data;
};
class MyClass
{
public:
    static int data;
};
class MyClass
{
public:
    // the compiler won't even create a symbol
    static const int data = 42;
};

Reason: some compilers encode the CV-qualifiers of the global data in its mangled name. Especially note that a static const value declared in the class itself can be considered for "inlining" -- that is, the compiler doesn't need to generate an external symbol for the value since all implementations are guaranteed to know it.

이유 : 몇몇 컴파일러는 CV-수식자를 맹글링된 이름에 부호화 하여 추가합니다. 특별히 정적 상수형 값을 클래스 내에서 선언 한다면 이들을 "인라인화" 된 것으로 간주 될 수 있습니다. 이 말은 컴파일러가 모든 구현물 (함수/클래스) 들이 존재를 알 수 있음을 보장할 수 있게 하는 값에 대한 외부 심볼을 생성해야 한다는 필요성을 가지지 않게 해줍니다.

Even for compilers that don't encode the CV-qualifiers of global data, adding const may make the compiler place the variable in a read-only section of memory. Code that tried to write it will probably crash.

전역 변수에 대한 CV-수식자를 부호로 추가하지 않는 컴파일러라 하더라도, const (상수화) 를 추가한 다는 것은 변수를 메모리 (저장공간) 의 읽기-전용 영역으로 옮긴다는 것을 의미할 수 있습니다. 그 값을 쓰기 시도를 하는 코드는 아마도 크래쉬를 발생시킬 것입니다.

Add a virtual member function to a class without any

가상 맴버 함수가 없었던 클래스에서 가상 맴버 함수를 추가하기
BeforeAfter
struct Data
{
    int i;
};
struct Data
{
    int i;
    virtual int j();
};

Reason: a class without any virtual members or bases is guaranteed to be exactly like a C structure, for compatibility with that language (that is a POD structure). On some compilers, structures/classes with bases that are POD themselves are also POD. However, as soon as there's one virtual base or virtual member function, the compiler is free to arrange the structure in a C++ manner, which usually means inserting a hidden pointer at the beginning or the end of the structure, pointing to the virtual table of that class. This changes the size and offset of the elements in the structure.

이유 : 가상 맴버나 상속이 하나라도 없는 클래스는 호환성을 목적으로 정확히 C 구조 형태로 존재되어야 합니다. (이것이 POD 구조 입니다.) 몇몇 컴파일러에서는 구조체와 POD 형태의 부모 클래스를 가지고 있는 클래스 또한 POD 구조로 관리합니다. 하지만, 가상 부모 클래스나 가상 맴버 함수를 하나라도 가지게 되면, 컴파일러는 자유롭게 C++ 형식으로 구조를 변경하는데, 이는 보통 그 클래스의 가상 테이블을 가리키는 숨은 포인터를 그 자료 구조의 시작또는 끝에 추가함을 의미합니다. 이러한 변화는 자료 구조의 구성 요소의 크기나 오프셋 (존재위치) 를 변경 시킵니다.

Add new virtuals to a non-leaf class

단말이 아닌 클래스에 가상 함수들을 추가

BeforeAfter
class MyClass
{
public:
    virtual ~MyClass();
    virtual void foo();
};
class MyClass
{
public:
    virtual ~MyClass();
    virtual void foo();
    virtual void bar();
};

Reason: the addition of a new virtual function to a class that is non-leaf (that is, there is at least one class deriving from this class) changes the layout of the virtual table (the virtual table is basically a list of function pointers, pointing to the functions that are active in this class level). To accommodate the new virtual, the compiler must add a new entry to this table, but existing derived classes won't know about it and will not have the entry in their virtual tables.

이유 : 단말-이 아닌 클래스(다시 말해, 적어도 하나의 클래스가 이 클래스로 부터 상속받아 생성됩니다.)에 새로운 가상 함수들을 추가한다는 것은 가상 테이블의 레이아웃 (구조) 를 변경하게 됩니다. (가상 테이블은 기본적으로 클래스 수준에서 활동하고 있는 함수들을 가리키는 함수 포인터의 목록입니다.) 새로운 가상 함수를 수용하기 위해, 컴파일러는 반드시 테이블에 새로운 항목을 추가해야 하지만, 이전에 상속아서 기존의 존재하는 클래스들은 그들의 가상 테이블에서 그 항목을 알지도 못하고 가지지도 않을 것입니다.

Change the order of the declaration of virtual functions

선언된 가상 함수의 순서를 변경
BeforeAfter
class MyClass
{
public:
    virtual ~MyClass();
    virtual void foo();
    virtual void bar();
};
class MyClass
{
public:
    virtual ~MyClass();
    virtual void bar();
    virtual void foo();
};

Reason: the compiler places the pointers to the functions implementing the virtual functions in the order that they are declared in the class. By changing the order of the declaration, the order of the entries in the virtual table changes too.

이유 : 클래스에서 가상 함수들이 선언된 순서대로 컴파일러가 이들을 가리키는 포인터들을 위치시킵니다. 선언된 순서를 변경함으로, 가상 테이블 항목들의 순서도 변경됩니다.

Note: the order is inherited from the parent classes, so overriding a virtual will allocate the entry in the parent's order.

참고 : 가상 함수의 순서는 부모 클래스에서 상속 받으므로, 가상함수를 오버라이드 하는 것은 부모 클래스의 순서로 된 항목들을 할당 할 것입니다.

Override a virtual that doesn't come from a primary base

주 (1차) 클래스에서 존재 않은 가상 함수를 오버라이드 하기
class PrimaryBase
{
public:
    virtual ~PrimaryBase();
    virtual void foo();
};
 
class SecondaryBase
{
public:
    virtual ~SecondaryBase();
    virtual void bar();
};
BeforeAfter
class MyClass: public PrimaryBase, public SecondaryBase
{
public:
    ~MyClass();
    void foo();
};
class MyClass: public PrimaryBase, public SecondaryBase
{
public:
    ~MyClass();
    void foo();
    void bar();
};

Reason: this is a tricky case. When dealing with multiple-inheritance of classes with virtual tables, the compiler must create multiple virtual tables to guarantee polymorphism works (that is, when your MyClass object is stored in a PrimaryBase or SecondaryBase pointer). The virtual table for the primary base is shared with the class's own virtual table, because they have the same layout at the beginning. However, if you override a virtual coming from a non-primary base, it is the same as adding a new virtual, since that primary base did not have the virtual by that name.

이유 : 이 경우는 까다로운 사례입니다. 가상 테이블을 가진 다중-상속의 클래스를 다루게 될때, 컴파일러는 다형성을 보장하기 위해 다수의 가상 테이블을 만들어야 합니다. (이는 MyClass 객체는 PrimaryBase 또는 SecondaryBase 포인터를 저장하고 있습니다.) 1차 부모 클래스에 대한 가상 테이블은 시작부터 동일한 레이아웃을 가지므로, 클래스 자신만의 가상 테이블을 공유하게 됩니다. 하지만, 1차 부모 클래스에 존재하지 않는 가상 함수를 오버라이드 한다면, 1차 부모 클래스는 그 이름을 가진 가상 함수를 가지고 있지 않으므로, 새로운 가상 함수를 추가하는 것과 동일한 효과를 가집니다.

Note: this applies to any case of multiple-inheritance, even if it's not a direct base. In the example above, if we had MyOtherClass deriving from MyClass, the same restriction would apply.

참고 : 다이렉트 베이스 (direct base, 1차 부모에서 직접 상속 받은 클래스) 가 아니어도 다중 상속을 가졌다면 어떤 경우에도 적용이 됩니다. 위 예제에서 만약 우리가 MyClass 를 상속받은 MyOtherClass 를 가지고 있다고 해도 같은 제한이 적용될 것입니다.

Override a virtual with a covariant return with different top address

다른 상위 주소를 반환하는 공변 반환 형 가상함수를 오버라이드 하기

struct Data1 { int i; };
class BaseClass
{
public:
    virtual Data1 *get();
};
 
struct Data0 { int i; };
struct Complex1: Data0, Data1 { };
struct Complex2: virtual Data1 { };
BeforeAfter
class MyClass: public BaseClass
{
public:
};
class MyClass: public BaseClass
{
public:
    Complex1 *get();
};
class MyClass: public BaseClass
{
public:
};
class MyClass: public BaseClass
{
public:
    Complex2 *get();
};

Reason: this is another tricky case, like the above one and also for the same reason: the compiler must add a second entry to the virtual table, just as if a new virtual function had been added, which changes the layout of the virtual table and breaks derived classes.

이유 : 이것 또한 까다로운 사례로, 위에 사례와 비슷하거나 동일한 이유입니다 : 만약 새로운 가상 함수가 추가되었다면, 컴파일러는 반드시 가상 테이블에 들어갈 두번째 항목을 만들어야 하고, 이는 가상 테이블의 레이아웃 (구조)를 변경시키고 상속 받은 클래스들의 내용을 바꾸게 합니다.

Covariant calls happen when the function overriding a virtual from a parent class returns a class different from the parent (this is allowed by the C++ standard, so the code above is perfectly valid and calling p->get() with p of type BaseClass will call MyClass::get). If the more-derived type doesn't have the same top-address such as Complex1 and Complex2 above, when compared to Data1, then the compiler needs to generate a stub function (usually called a "thunk") to adjust the value of the pointer returned. It places the address to that thunk in the entry corresponding to the parent's virtual function in the virtual table. However, it also adds a new entry for calls made which return the new top-address.

공변 호출은 하나의 부모 클래스에 있는 가상 함수를 오버라이드 하여 다른 부모에 있는 클래스를 반환하게 할 때 발생합니다. (이런 방식은 C++ 표준에서 허용하기 때문에, BaseClass 형의 p 에서 p->get() 을 호출하면 MyClass::get 을 얻게 되는, 위의 코드는 완벽히 유효합니다.) 만약 Data1 과 비교 했을때, 더-상속 받은 자료형(자식 클래스) 가 Complex1 과 Complex2 와 같은 상위-주소를 가지지(접근하지) 않는다면, 컴파일러는 포인터가 반환되는 값을 조절하기 위해 스텁 함수 (내용은 없는 껍데기) 를 생성합니다. (일반적으로 "썽크" 라고 불립니다.) 그 함수는 부모의 가상 테이블에 위치한 가상 함수에 대응하는 썽크(다른 서브루틴을 위해 자동으로 만들어지는 함수) 주소에 위치합니다. 하지만, 새로운 상위-주소를 반환하는 호출을 위한 새로운 항목 또한 추가됩니다.

Content is available under Creative Commons License SA 3.0 as well as the GNU Free Documentation License 1.2.