문제

나는 최근 인터뷰에서 가상 함수와 다중 상속이 포함된 객체 레이아웃에 대한 질문을 받았습니다.
다중 상속을 포함하지 않고 구현되는 방법의 맥락에서 설명했습니다(예:컴파일러가 가상 테이블을 생성한 방법, 각 개체의 가상 테이블에 대한 비밀 포인터 삽입 등).
제 설명에 뭔가 빠진 부분이 있는 것 같았습니다.
질문은 다음과 같습니다(아래 예 참조).

  1. 클래스 C 객체의 정확한 메모리 레이아웃은 무엇입니까?
  2. 클래스 C에 대한 가상 테이블 항목입니다.
  3. 클래스 A, B, C 객체의 크기(sizeof로 반환됨)(8, 8, 16 ??)
  4. 가상 상속을 사용하면 어떻게 될까요?확실히 크기와 가상 테이블 항목이 영향을 받아야 합니까?

예제 코드:

class A {  
  public:   
    virtual int funA();     
  private:  
    int a;  
};

class B {  
  public:  
    virtual int funB();  
  private:  
    int b;  
};  

class C : public A, public B {  
  private:  
    int c;  
};   

감사해요!

도움이 되었습니까?

해결책

메모리 레이아웃과 vtable 레이아웃은 컴파일러에 따라 다릅니다. 예를 들어 내 GCC를 사용하면 다음과 같습니다.

sizeof(int) == 4
sizeof(A) == 8
sizeof(B) == 8
sizeof(C) == 20

VTable 포인터에 필요한 공간의 크기 (int)와 컴파일러에서 컴파일러, 플랫폼마다 다를 수 있습니다. (c) == 20이 아닌 16 세가 아닌 이유는 GCC가 A 서브 버젝트의 경우 8 바이트, B 서브 버젝트의 경우 8 바이트 및 회원의 경우 4 바이트를 제공하기 때문입니다. int c.

Vtable for C
C::_ZTV1C: 6u entries
0     (int (*)(...))0
4     (int (*)(...))(& _ZTI1C)
8     A::funA
12    (int (*)(...))-0x00000000000000008
16    (int (*)(...))(& _ZTI1C)
20    B::funB

Class C
   size=20 align=4
   base size=20 base align=4
C (0x40bd5e00) 0
    vptr=((& C::_ZTV1C) + 8u)
  A (0x40bd6080) 0
      primary-for C (0x40bd5e00)
  B (0x40bd60c0) 8
      vptr=((& C::_ZTV1C) + 20u)

가상 상속 사용

class C : public virtual A, public virtual B

레이아웃이 변경됩니다

Vtable for C
C::_ZTV1C: 12u entries
0     16u
4     8u
8     (int (*)(...))0
12    (int (*)(...))(& _ZTI1C)
16    0u
20    (int (*)(...))-0x00000000000000008
24    (int (*)(...))(& _ZTI1C)
28    A::funA
32    0u
36    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& _ZTI1C)
44    B::funB

VTT for C
C::_ZTT1C: 3u entries
0     ((& C::_ZTV1C) + 16u)
4     ((& C::_ZTV1C) + 28u)
8     ((& C::_ZTV1C) + 44u)

Class C
   size=24 align=4
   base size=8 base align=4
C (0x40bd5e00) 0
    vptridx=0u vptr=((& C::_ZTV1C) + 16u)
  A (0x40bd6080) 8 virtual
      vptridx=4u vbaseoffset=-0x0000000000000000c vptr=((& C::_ZTV1C) + 28u)
  B (0x40bd60c0) 16 virtual
      vptridx=8u vbaseoffset=-0x00000000000000010 vptr=((& C::_ZTV1C) + 44u)

GCC를 사용하면 추가 할 수 있습니다 -fdump-class-hierarchy 이 정보를 얻으려면.

다른 팁

여러 상속으로 기대해야 할 것은 (일반적으로 첫 번째) 서브 클래스로 캐스팅 할 때 포인터가 변경 될 수 있다는 것입니다. 인터뷰 질문을 디버깅하고 답변하는 동안 알아야 할 사항.

첫째, 다형성 클래스에는 하나 이상의 가상 함수가 있으므로 vptr이 있습니다.

struct A {
    virtual void foo();
};

다음과 같이 컴파일됩니다.

struct A__vtable { // vtable for objects of declared type A
    void (*foo__ptr) (A *__this); // pointer to foo() virtual function
};

void A__foo (A *__this); // A::foo ()

// vtable for objects of real (dynamic) type A
const A__vtable A__real = { // vtable is never modified
    /*foo__ptr =*/ A__foo
};

struct A {
    A__vtable const *__vptr; // ptr to const not const ptr
                             // vptr is modified at runtime
};

// default constructor for class A (implicitly declared)
void A__ctor (A *__that) { 
    __that->__vptr = &A__real;
}

주목:C++는 C와 같은 다른 고급 언어(cfront가 그랬던 것처럼) 또는 심지어 C++ 하위 집합(여기서는 C++가 없는 C++)으로 컴파일될 수 있습니다. virtual).나는 넣었다 __ 컴파일러 생성 이름에서.

참고로 이는 단순한 RTTI가 지원되지 않는 모델;실제 컴파일러는 vtable에 데이터를 추가하여 지원합니다. typeid.

이제 간단한 파생 클래스는 다음과 같습니다.

struct Der : A {
    override void foo();
    virtual void bar();
};

가상이 아닌(*) 기본 클래스 하위 개체는 멤버 하위 개체와 같은 하위 개체이지만, 멤버 하위 개체는 완전한 개체입니다.실제(동적) 유형은 선언된 유형이고 기본 클래스 하위 객체는 완전하지 않으며 실제 유형은 생성 중에 변경됩니다.

(*) 가상 멤버 기능이 비 가상 멤버와 다른 것처럼 가상 기반은 매우 다릅니다.

struct Der__vtable { // vtable for objects of declared type Der
    A__vtable __primary_base; // first position
    void (*bar__ptr) (Der *__this); 
};

// overriding of a virtual function in A:
void Der__foo (A *__this); // Der::foo ()

// new virtual function in Der:
void Der__bar (Der *__this); // Der::bar ()

// vtable for objects of real (dynamic) type Der
const Der__vtable Der__real = { 
    { /*foo__ptr =*/ Der__foo },
    /*foo__ptr =*/ Der__bar
};

struct Der { // no additional vptr
    A __primary_base; // first position
};

여기서 "첫 번째 위치"는 해당 멤버가 첫 번째여야 함을 의미합니다(다른 멤버는 다시 정렬될 수 있음).그들은 오프셋 0에 위치하므로 우리는 reinterpret_cast 포인터, 유형은 호환 가능합니다.0이 아닌 오프셋에서는 산술 연산을 사용하여 포인터 조정을 수행해야 합니다. char*.

조정 부족은 생성된 코드 측면에서 큰 문제가 아닌 것처럼 보일 수 있지만(일부는 즉시 asm 명령어를 추가함) 그 이상을 의미하며 이러한 포인터가 다른 유형을 갖는 것으로 볼 수 있음을 의미합니다.유형의 객체 A__vtable* 에 대한 포인터를 포함할 수 있습니다. Der__vtable 다음 중 하나로 취급됩니다. Der__vtable* 또는 A__vtable*.동일한 포인터 개체가 포인터 역할을 합니다. A__vtable 유형의 객체를 다루는 함수에서 A 그리고 a에 대한 포인터로 Der__vtable 유형의 객체를 다루는 함수에서 Der.

// default constructor for class Der (implicitly declared)
void Der__ctor (Der *__this) { 
    A__ctor (reinterpret_cast<A*> (__this));
    __this->__vptr = reinterpret_cast<A__vtable const*> (&Der__real);
}

vptr에 정의된 동적 유형은 vptr에 새 값을 할당하므로 생성 중에 변경되는 것을 볼 수 있습니다(이 특별한 경우 기본 클래스 생성자에 대한 호출은 유용하지 않으며 최적화할 수 있지만 그렇지 않습니다. 사소하지 않은 생성자의 경우).

다중 상속의 경우:

struct C : A, B {};

C 인스턴스에는 A 그리고 B, 이렇게:

struct C {
    A base__A; // primary base
    B base__B;
};

이러한 기본 클래스 하위 개체 중 하나만 오프셋 0에 위치할 수 있는 권한을 가질 수 있습니다.이는 여러 면에서 중요합니다.

  • 다른 베이스 클래스(업캐스트)에 대한 포인터 변환에는 조절;반대로 업캐스트에는 반대 조정이 필요합니다.

  • 이는 기본 클래스로 가상 호출을 수행할 때 포인터, this 파생된 항목의 항목에 대한 올바른 값을 가집니다. 클래스 오버라이드.

따라서 다음 코드는 다음과 같습니다.

void B::printaddr() {
    printf ("%p", this);
}

void C::printaddr () { // overrides B::printaddr()
    printf ("%p", this);
}

다음과 같이 컴파일할 수 있습니다.

void B__printaddr (B *__this) {
    printf ("%p", __this);
}

// proper C::printaddr taking a this of type C* (new vtable entry in C)
void C__printaddr (C *__this) {
    printf ("%p", __this);
}

// C::printaddr overrider for B::printaddr
// needed for compatibility in vtable
void C__B__printaddr (B *__this) {
    C__printaddr (reinterpret_cast<C*>(reinterpret_cast<char*> (__this) - offset__C__B));
}

우리는 C__B__printaddr 선언된 유형 및 의미 체계는 다음과 호환됩니다. B__printaddr, 그래서 우리는 사용할 수 있습니다 &C__B__printaddr vtable에서 B; C__printaddr 호환되지는 않지만 다음과 관련된 통화에 사용할 수 있습니다. C 객체 또는 클래스에서 파생됨 C.

가상 멤버가 아닌 함수는 내부 항목에 액세스할 수 있는 무료 함수와 같습니다.가상 멤버 함수는 재정의를 통해 사용자 정의할 수 있는 "유연성 지점"입니다.가상 멤버 함수 선언은 클래스 정의에서 특별한 역할을 합니다.다른 멤버와 마찬가지로 외부 세계와의 계약의 일부이지만 동시에 파생 클래스와의 계약의 일부이기도 합니다.

가상이 아닌 기본 클래스는 재정의를 통해 동작을 구체화할 수 있는 멤버 개체와 같습니다(또한 보호된 멤버에 액세스할 수도 있음).외부 세계에 대한 상속 A ~에 Der 포인터에 대해 암시적인 파생-기본 변환이 존재한다는 것을 의미합니다. A& 에 바인딩될 수 있습니다. Der lvalue 등추가 파생 클래스의 경우(다음에서 파생됨) Der), 이는 또한 가상 기능을 의미합니다. A 에서 상속됩니다. Der:가상 기능 A 추가 파생 클래스에서 재정의될 수 있습니다.

클래스가 더 파생되면 다음과 같이 말하십시오. Der2 에서 파생됩니다 Der, 암시적 변환 유형의 포인터 Der2* 에게 A* 의미상 다음 단계에서 수행됩니다.먼저, Der* 검증되었습니다(상속 관계에 대한 액세스 제어 Der2 ~에서 Der 일반적인 공개/보호/비공개/친구 규칙으로 확인된 후)의 액세스 제어가 이루어집니다. Der 에게 A.가상이 아닌 상속 관계는 파생 클래스에서 재정의하거나 재정의할 수 없습니다.

가상 멤버가 아닌 함수는 직접 호출할 수 있고 가상 멤버는 vtable을 통해 간접적으로 호출해야 합니다(실제 객체 유형이 컴파일러에 의해 알려지는 경우는 제외). virtual 키워드는 멤버 함수 액세스에 대한 간접 참조를 추가합니다.함수 멤버와 마찬가지로 virtual 키워드는 기본 개체 액세스에 대한 간접 참조를 추가합니다.함수와 마찬가지로 가상 기본 클래스는 상속에 유연성을 추가합니다.

비가상, 반복, 다중 상속을 수행하는 경우:

struct Top { int i; };
struct Left : Top { };
struct Right : Top { };
struct Bottom : Left, Right { };

딱 2개 있어요 Top::i 하위 객체 Bottom (Left::i 그리고 Right::i), 멤버 객체와 마찬가지로:

struct Top { int i; };
struct mLeft { Top t; };
struct mRight { mTop t; };
struct mBottom { mLeft l; mRight r; }

둘이 있다는 사실에 누구도 놀라지 않는다 int 하위 회원(l.t.i 그리고 r.t.i).

가상 기능 사용:

struct Top { virtual void foo(); };
struct Left : Top { }; // could override foo
struct Right : Top { }; // could override foo
struct Bottom : Left, Right { }; // could override foo (both)

이는 두 개의 서로 다른 (관련되지 않은) 가상 함수가 있음을 의미합니다. foo, 고유한 vtable 항목이 있습니다(둘 다 동일한 서명을 가지므로 공통 재정의자를 가질 수 있음).

가상이 아닌 기본 클래스의 의미는 가상이 아닌 기본 상속이 배타적 관계라는 사실에서 따릅니다.Left와 Top 사이에 설정된 상속 관계는 더 이상의 파생으로 수정될 수 없으므로, Left와 Top 사이에 유사한 관계가 존재한다는 사실은 Right 그리고 Top 이 관계에 영향을 미칠 수 없습니다.특히, 다음을 의미합니다. Left::Top::foo() 에서 재정의할 수 있습니다. Left 그리고 Bottom, 하지만 Right, 와 상속 관계가 없습니다. Left::Top, 이 사용자 정의 지점을 설정할 수 없습니다.

가상 기본 클래스는 다릅니다.가상 상속은 파생 클래스에서 사용자 정의할 수 있는 공유 관계입니다.

struct Top { int i; virtual void foo(); };
struct vLeft : virtual Top { }; 
struct vRight : virtual Top { };
struct vBottom : vLeft, vRight { }; 

여기서는 단 하나의 기본 클래스 하위 개체입니다. Top, 하나만 int 회원.

구현:

가상이 아닌 기본 클래스를 위한 공간은 파생 클래스의 고정 오프셋이 있는 정적 레이아웃을 기반으로 할당됩니다.파생 클래스의 레이아웃은 더 많은 파생 클래스의 레이아웃에 포함되므로 하위 객체의 정확한 위치는 객체의 실제(동적) 유형에 의존하지 않습니다(가상이 아닌 함수의 주소가 상수인 것처럼). ).OTOH, 가상 상속이 있는 클래스의 하위 개체 위치는 동적 유형에 의해 결정됩니다(가상 함수 구현 주소는 동적 유형이 알려진 경우에만 알 수 있는 것과 같습니다).

하위 개체의 위치는 런타임에 vptr 및 vtable(기존 vptr을 재사용하면 공간 오버헤드가 줄어듦) 또는 하위 개체에 대한 직접 내부 포인터(더 많은 오버헤드, 더 적은 간접 참조 필요)를 사용하여 결정됩니다.

가상 기본 클래스의 오프셋은 완전한 객체에 대해서만 결정되고, 선언된 특정 유형에 대해서는 알 수 없기 때문에 가상 베이스는 오프셋 0에 할당될 수 없으며 절대 기본 베이스가 아닙니다..파생 클래스는 가상 기반의 vptr을 자체 vptr로 재사용하지 않습니다.

가능한 번역 측면에서:

struct vLeft__vtable { 
    int Top__offset; // relative vLeft-Top offset
    void (*foo__ptr) (vLeft *__this); 
    // additional virtual member function go here
};

// this is what a subobject of type vLeft looks like
struct vLeft__subobject { 
    vLeft__vtable const *__vptr;
    // data members go here
};

void vLeft__subobject__ctor (vLeft__subobject *__this) { 
    // initialise data members
}

// this is a complete object of type vLeft 
struct vLeft__complete {
    vLeft__subobject __sub;
    Top Top__base;
}; 

// non virtual calls to vLeft::foo
void vLeft__real__foo (vLeft__complete *__this);

// virtual function implementation: call via base class
// layout is vLeft__complete 
void Top__in__vLeft__foo (Top *__this) {
    // inverse .Top__base member access 
    char *cp = reinterpret_cast<char*> (__this);
    cp -= offsetof (vLeft__complete,Top__base);
    vLeft__complete *__real = reinterpret_cast<vLeft__complete*> (cp);
    vLeft__real__foo (__real);
}

void vLeft__foo (vLeft *__this) {
    vLeft__real__foo (reinterpret_cast<vLeft__complete*> (__this));
}

// Top vtable for objects of real type vLeft
const Top__vtable Top__in__vLeft__real = { 
    /*foo__ptr =*/ Top__in__vLeft__foo 
};

// vLeft vtable for objects of real type vLeft
const vLeft__vtable vLeft__real = { 
    /*Top__offset=*/ offsetof(vLeft__complete, Top__base),
    /*foo__ptr =*/ vLeft__foo 
};

void vLeft__complete__ctor (vLeft__complete *__this) { 
    // construct virtual bases first
    Top__ctor (&__this->Top__base); 

    // construct non virtual bases: 
    // change dynamic type to vLeft
    // adjust both virtual base class vptr and current vptr
    __this->Top__base.__vptr = &Top__in__vLeft__real;
    __this->__vptr = &vLeft__real;

    vLeft__subobject__ctor (&__this->__sub);
}

알려진 유형의 객체의 경우 기본 클래스에 대한 액세스는 다음을 통해 이루어집니다. vLeft__complete:

struct a_vLeft {
    vLeft m;
};

void f(a_vLeft &r) {
    Top &t = r.m; // upcast
    printf ("%p", &t);
}

다음으로 번역됩니다:

struct a_vLeft {
    vLeft__complete m;
};

void f(a_vLeft &r) {
    Top &t = r.m.Top__base;
    printf ("%p", &t);
}

여기서는 실제(동적) 유형의 r.m 알려져 있으며 하위 객체의 상대적 위치도 컴파일 타임에 알려져 있습니다.하지만 여기는:

void f(vLeft &r) {
    Top &t = r; // upcast
    printf ("%p", &t);
}

실제(동적) 유형 r 알 수 없으므로 vptr을 통해 액세스합니다.

void f(vLeft &r) {
    int off = r.__vptr->Top__offset;
    char *p = reinterpret_cast<char*> (&r) + off;
    printf ("%p", p);
}

이 함수는 레이아웃이 다른 모든 파생 클래스를 허용할 수 있습니다.

// this is what a subobject of type vBottom looks like
struct vBottom__subobject { 
    vLeft__subobject vLeft__base; // primary base
    vRight__subobject vRight__base; 
    // data members go here
};

// this is a complete object of type vBottom 
struct vBottom__complete {
    vBottom__subobject __sub; 
    // virtual base classes follow:
    Top Top__base;
}; 

참고 vLeft 기본 클래스는 고정된 위치에 있습니다. vBottom__subobject, 그래서 vBottom__subobject.__ptr 전체에 대한 vptr로 사용됩니다. vBottom.

의미론:

상속 관계는 모든 파생 클래스에서 공유됩니다.이는 재정의할 수 있는 권한이 공유된다는 의미입니다. vRight 재정의할 수 있음 vLeft::foo.이는 책임 공유를 생성합니다. vLeft 그리고 vRight 맞춤설정 방법에 동의해야 합니다. Top:

struct Top { virtual void foo(); };
struct vLeft : virtual Top { 
    override void foo(); // I want to customise Top
}; 
struct vRight : virtual Top { 
    override void foo(); // I want to customise Top
}; 
struct vBottom : vLeft, vRight { };  // error

여기서 우리는 충돌을 봅니다: vLeft 그리고 vRight 유일한 foo 가상 함수의 동작을 정의하려고 합니다. vBottom 공통 재정의자가 부족하여 정의에 오류가 있습니다.

struct vBottom : vLeft, vRight  { 
    override void foo(); // reconcile vLeft and vRight 
                         // with a common overrider
};

구현:

가상이 아닌 기본 클래스와 가상이 아닌 기본 클래스가 있는 클래스 생성에는 멤버 변수에 대해 수행된 것과 동일한 순서로 기본 클래스 생성자를 호출하고 ctor를 입력할 때마다 동적 유형을 변경하는 작업이 포함됩니다.생성하는 동안 기본 클래스 하위 개체는 실제로 완전한 개체인 것처럼 작동합니다. 이는 불가능한 완전한 추상 기본 클래스 하위 개체의 경우에도 마찬가지입니다.정의되지 않은(순수한) 가상 기능을 가진 객체입니다.가상 함수와 RTTI는 생성 중에 호출될 수 있습니다(물론 순수 가상 함수는 제외).

가상 기반이 있는 가상이 아닌 기본 클래스를 사용하여 클래스를 구성하는 것은 더 복잡합니다.:생성하는 동안 동적 유형은 기본 클래스 유형이지만 가상 기본의 레이아웃은 여전히 ​​아직 생성되지 않은 가장 많이 파생된 유형의 레이아웃이므로 이 상태를 설명하려면 더 많은 vtable이 필요합니다.

// vtable for construction of vLeft subobject of future type vBottom
const vLeft__vtable vLeft__ctor__vBottom = { 
    /*Top__offset=*/ offsetof(vBottom__complete, Top__base),
    /*foo__ptr =*/ vLeft__foo 
};

가상 기능은 vLeft (구성 중에는 vBottom 개체 수명이 시작되지 않았습니다.) 가상 기본 위치는 vBottom (에 정의된 대로 vBottom__complete 이의를 제기했습니다).

의미론:

초기화하는 동안 객체가 초기화되기 전에 객체를 사용하지 않도록 주의해야 한다는 것은 분명합니다.C++에서는 객체가 완전히 초기화되기 전에 이름을 제공하므로 그렇게 하기가 쉽습니다.

int foo (int *p) { return *pi; }
int i = foo(&i); 

또는 생성자에서 this 포인터를 사용합니다.

struct silly { 
    int i;
    std::string s;
    static int foo (bad *p) { 
        p->s.empty(); // s is not even constructed!
        return p->i; // i is not set!
    }
    silly () : i(foo(this)) { }
};

어떤 용도로 사용하든 매우 분명합니다. this ctor-init-list에서 주의 깊게 확인해야 합니다.모든 멤버 초기화 후, this 다른 함수에 전달되고 일부 세트에 등록될 수 있습니다(파괴가 시작될 때까지).

덜 분명한 것은 공유 가상 베이스와 관련된 클래스를 생성할 때 하위 객체 생성이 중단된다는 것입니다.건설하는 동안 vBottom:

  • 먼저 가상 기지가 건설됩니다:언제 Top 구성되면 일반 주제(Top 가상 베이스인지도 모릅니다)

  • 그런 다음 기본 클래스는 왼쪽에서 오른쪽 순서로 구성됩니다.그만큼 vLeft 하위 객체가 구성되어 일반 객체로 기능하게 됩니다. vLeft (하지만 vBottom 레이아웃) 따라서 Top 이제 기본 클래스 하위 객체에는 vLeft 동적 유형;

  • 그만큼 vRight 하위 객체 생성이 시작되고 기본 클래스의 동적 유형이 vRight로 변경됩니다.하지만 vRight 에서 파생되지 않습니다 vLeft, 아무것도 몰라 vLeft, 그래서 vLeft 이제 베이스가 깨졌습니다.

  • 신체가 Bottom 생성자가 시작되면 모든 하위 객체의 유형이 안정화되고 vLeft 다시 작동합니다.

정렬 또는 패딩 비트에 대한 언급 없이이 답변이 어떻게 완전한 답변으로 취해질 수 있는지 잘 모르겠습니다.

약간의 정렬 배경을 알려 드리겠습니다.

"메모리 주소 A는 a가 n 바이트의 배수 (여기서 n은 2의 전력) 일 때 n-byte가 정렬된다고합니다.이 맥락에서 바이트는 가장 작은 메모리 액세스 단위입니다. 즉, 각 메모리 주소는 지정됩니다. 다른 바이트. n-byte 정렬 된 주소는 이진으로 표현 될 때 Log2 (n)가 가장 중요하지 않은 0을 갖습니다.

대체 문구 B- 비트 정렬 된 AB/8 바이트 정렬 주소를 지정합니다 (예 : 64 비트 정렬은 8 바이트 정렬).

액세스하는 데이텀이 n 바이트 길이이고 데이텀 주소가 n-byte 정렬 될 때 메모리 액세스가 정렬된다고합니다. 메모리 액세스가 정렬되지 않으면 잘못 정렬 된 것으로 알려져 있습니다. 정의에 따라 BYTE 메모리 액세스는 항상 정렬됩니다.

N- 바이트가 정렬 된 주소 만 포함하도록 허용되는 경우 N- 바이트 길이의 원시 데이터를 지칭하는 메모리 포인터는 정렬되지 않은 경우 정렬된다고합니다. 그렇지 않으면 정렬되지 않은 것으로 알려져 있습니다. 데이터 집계 (데이터 구조 또는 배열)를 지칭하는 메모리 포인터는 골재의 각 원시 기준이 정렬 된 경우 (및 경우에만) 정렬됩니다.

위의 정의는 각 원시 데이텀이 두 바이트의 힘이라고 가정합니다. 그렇지 않은 경우 (X86의 80 비트 부동 소수점과 마찬가지로) 컨텍스트는 데이텀이 정렬 된 것으로 간주되는 조건에 영향을 미칩니다.

데이터 구조는 스택에 메모리에 저장 될 수 있으며, 바인딩되지 않은 것으로 알려진 동적 크기로 경계로 알려진 정적 크기 또는 힙에 저장 될 수 있습니다. " - Wiki .... Wiki ...

정렬을 유지하기 위해 컴파일러는 구조/클래스 객체의 컴파일 된 코드에 패딩 비트를 삽입합니다. "컴파일러 (또는 통역사)는 일반적으로 정렬 된 경계에 개별 데이터 항목을 할당하지만 데이터 구조에는 종종 정렬 요구 사항이 다른 구성원이 있습니다. 적절한 정렬을 유지하기 위해 번역기는 일반적으로 추가되지 않은 데이터 구성원을 삽입하여 각 구성원이 올바르게 정렬되도록합니다. 전체 데이터 구조는 최종 이름이없는 멤버로 패딩 될 수 있습니다. 이는 다양한 구조의 각 구성원이 올바르게 정렬 될 수 있습니다. .... .... ....

패딩은 구조 멤버가 더 큰 정렬 요구 사항을 가진 멤버 또는 구조 끝에 멤버가 뒤 따르는 경우에만 삽입됩니다. "-Wiki

GCC가 어떻게하는지에 대한 자세한 정보를 얻으려면

http://www.delorie.com/gnu/docs/gcc/gccint_111.html

그리고 "기본-정렬"텍스트를 검색합니다.

이제이 문제를 겪게합시다.

예제 클래스를 사용하여 64 비트 우분투에서 실행되는 GCC 컴파일러를 위해이 프로그램을 만들었습니다.

int main() {
    cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
    A objA;
    C objC;
    cout<<__alignof__(objA.a)<<endl;
    cout<<sizeof(void*)<<endl;
    cout<<sizeof(int)<<endl;
    cout<<sizeof(A)<<endl;
    cout<<sizeof(B)<<endl;
    cout<<sizeof(C)<<endl;
    cout<<__alignof__(objC.a)<<endl;
    cout<<__alignof__(A)<<endl;
    cout<<__alignof__(C)<<endl;
    return 0;
}

이 프로그램의 결과는 다음과 같습니다.

4
8
4
16
16
32
4
8
8

이제 설명하겠습니다. 두 A & B에는 가상 함수가 있으므로 별도의 vtables를 생성하고 객체의 시작 부분에 각각 VPTR이 추가됩니다.

따라서 클래스 A의 객체는 VPTR (vtable을 가리키는)과 int를 갖습니다. 포인터의 길이는 8 바이트이며 int의 길이는 4 바이트입니다. 따라서 컴파일하기 전에 크기는 12 바이트입니다. 그러나 컴파일러는 int A의 끝에 패딩 비트로 4 바이트를 추가합니다. 따라서 컴파일 후 A의 객체 크기는 12+4 = 16입니다.

클래스 B의 객체와 마찬가지로.

이제 C의 객체에는 2 개의 VPTR (각 클래스 A 및 클래스 B마다 하나씩)과 3 개의 int (a, b, c)가 있습니다. 따라서 크기는 8 (vptr a) + 4 (int a) + 4 (패딩 바이트) + 8 (vptr b) + 4 (int b) + 4 (int c) = 32 바이트 여야합니다. 따라서 C의 총 크기는 32 바이트입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top