سؤال

سألت مؤخرا في مقابلة حول تخطيط الكائنات مع الوظائف الافتراضية والميراث المتعدد المعني.
أوضحت ذلك في سياق كيفية تنفيذها دون ميراث متعددة المعني (أي كيف ولد التحويل البرمجي الجدول الظاهري، أدخل مؤشر سري إلى الجدول الظاهري في كل كائن وما إلى ذلك).
بدا لي أن هناك شيء مفقود في تفسيري.
حتى هنا أسئلة (انظر المثال أدناه)

  1. ما هو تخطيط الذاكرة الدقيقة لكائن الفئة C.
  2. إدخالات الجداول الافتراضية للفئة C.
  3. الأحجام (كما عادت من قبل Sizeof) من كائن الطبقات A، B و C. (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 على برنامج التحويل البرمجي الخاص بك. باستخدام بلدي دول مجلس التعاون الخليجي على سبيل المثال، تبدو مثل هذا:

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

لاحظ أن SizeOf (int) والمساحة اللازمة لمؤشر Vtable يمكن أن تختلف أيضا من برنامج التحويل البرمجي إلى برنامج التحويل البرمجي والنظام الأساسي إلى النظام الأساسي. السبب في السبب الذي يجعل المقايف (ج) == 20 وليس 16 هو أن دول مجلس التعاون الخليجي تعطيها 8 بايت من أجل subobject، 8 بايت من أجل BUBODJECT و 4 بايت لعضوها int c.

VTable for CC :: _ ztv1c: مقالات 6U 0 (int (*) (...) 0 4 (INT (*) (...) (& _zti1c) 8 a :: funa 12 (int (*) () ...). = ((& c :: _ _ ztv1c) + 8u) a (0x40bd6080) 0 formal-for c (0x40bd5e00) B (0x40bd60c0) 8 vptr = ((& c :: _ ztv1c) + 20u)

باستخدام الميراث الافتراضي

class C : public virtual A, public virtual B

يتغير التخطيط إلى

VTable ل CC :: _ ZTV1C: مقالات 12U 0 16U 4 8U 8 (int (*) (...) 0 12 (INT (*) (...) (& _zti1c) 16 0U 20 (INT (*) (...)) - 0x000000000000000000008 24 (INT (*) (...)) (& _ZTI1C) 28 ألف :: Funa 32 0U 36 (INT (*) (...) - 0x00000000000000010 40 (int (* (...)) (...)) (...) (& _zti1c) 44 b :: funb vtt for cc :: _ ztt1c: مقالات 3U 0 ((& c :: _ ztv1c) + 16u) 4 ((& c :: _ _ ztv1c) + 28u) 8 ((& c :: _ _ ztv1c) + 44u) الفئة C حجم = 24 محاذاة = 4 حجم قاعدة = 8 قاعدة محاذاة = 4 c (0x40bd5e00) 0 vptridx = 0u vptr = ((& c :: _ _ ztv1c) + 16u) a ( 0x40BD60808) 8 Virtual VPTRIDX = 4U vBaseOffset = -0x0000000000000000cc vptr = ((& c :: _ ztv1c) + 28u) b (0x40bd60c0) 16 virtual vptridx = 8u vbaseoffset = -0x00000000000000010 vptr = ((& c :: _ _ ztv1c) + 44u)

باستخدام دول مجلس التعاون الخليجي، يمكنك إضافة -fdump-class-hierarchy للحصول على هذه المعلومات.

نصائح أخرى

1 شيء يمكن توقعه مع الميراث المتعدد هو أن مؤشرك يمكن أن يتغير عند الصب إلى الفئة الفرعية (عادة ليس أولا). شيء يجب أن تكون على دراية به أثناء تصحيح الأخطاء والإجابة على أسئلة المقابلة.

أولا، يحتوي فئة متعدد الألوان على وظيفة افتراضية واحدة على الأقل، لذلك يحتوي على 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 DIS) أو حتى إلى مجموعة فرعية C ++ (هنا C ++ دون virtual). وضعت __ في مترجم ولدت الأسماء.

لاحظ أن هذا هو التبسيط نموذج حيث rtti غير مدعوم؛ سوف الجوبرجمات الحقيقية إضافة بيانات في VTable لدعم typeid.

الآن، فئة مشتقة بسيطة:

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

غير الظاهري (*) Subobjects Class Subobjects Subobjects مثل Subobjects الأعضاء، ولكن في حين أن Subobjects الأعضاء كائنات كاملة، أي. النوع الحقيقي (الديناميكي) هو نوعها المعلن، لا تكتمل Subobjects الفئة الأساسية، وتغيير نوعها الحقيقي أثناء البناء.

(*) قواعد افتراضية مختلفة جدا، مثل وظائف الأعضاء الافتراضية تختلف عن الأعضاء غير الافتراضيين

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
};

هنا يعني "المركز الأول" أن العضو يجب أن يكون أولا (يمكن إعادة طلب الأعضاء الآخرين): إنهم يقعون في إزاحة صفر حتى نتمكن reinterpret_cast المؤشرات، الأنواع متوافقين؛ في إزاحة غير صفرية، علينا أن نفعل تعديلات المؤشر مع حسابي على char*.

قد لا يبدو الافتقار إلى التعديل كصقة كبيرة في مدة التعليمات البرمجية التي تم إنشاؤها (فقط بعض التعليمات الفورية ASM)، ولكن هذا يعني أكثر بكثير من ذلك، فهذا يعني أن هذه المؤشرات يمكن أن ينظر إليها على أنها أنواع مختلفة: كائن من النوع A__vtable* يمكن أن تحتوي على مؤشر ل Der__vtable ويعامل معهم إما Der__vtable* أو أ A__vtable*. وبعد يعتمد كائن المؤشر نفسه بمثابة مؤشر إلى A__vtable في الوظائف التي تتعامل مع كائنات النوع 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 (في هذه الحالة بالذات، لا يوجد مكالمة إلى منشئ الطبقة الأساسية لا شيء مفيد ويمكن تحسينه بعيدا، لكنه غير صحيح T القضية مع منشئين غير تافهة).

مع ميراث متعددة:

struct C : A, B {};

أ C سيتم احتواء مثيل A و B, ، مثل هذا:

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

لاحظ أن واحدة فقط من هذه العناصر الفئة الأساسية يمكن أن تحتوي على امتياز الجلوس عند تعويض الصفر؛ هذا مهم بطرق عديدة:

  • يحتاج تحويل المؤشرات إلى الفصول الأساسية الأخرى (البريدية) إلى تعديل؛ على العكس من ذلك، تحتاج الثغيان إلى التعديلات المعاكسة؛

  • هذا يعني أنه عند إجراء مكالمة افتراضية مع مؤشر فئة أساسية، 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 { };

هناك اثنين فقط Top::i subobjects في 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 متميزة (كلاهما يكون لها نفس التوقيع، يمكن أن يكون لها تجاوز مشترك).

يتبع الدلالية من الفصول الأساسية غير الافتراضية من حقيقة أن الأساس وغير الظاهري والميراث هو علاقة حصرية: لا يمكن تعديل علاقة الميراث المنشأة بين اليسار والأعلى من خلال اشتقاق إضافي، لذلك حقيقة أن علاقة مماثلة موجودة بين 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 { }; 

هنا، هذا هو فقط فئة قاعدة واحدة subobject Top, ، واحد فقط int عضو.

تطبيق:

يتم تخصيص غرفة للفصول الأساسية غير الافتراضية بناء على تخطيط ثابت مع إزاحة ثابتة في الفئة المشتقة. لاحظ أن تخطيط الفئة المشتقة هو المدرجة في تخطيط فئة أكثر مشتقة، لذلك لا يعتمد الموضع الدقيق لل Subobjects على النوع الحقيقي (الديناميكي) من الكائن (تماما مثل عنوان الوظيفة غير الافتراضية هو ثابت ). يتم تحديد Otoh، موقف subobjects في فصل من الفئة مع الميراث الافتراضي بواسطة النوع الديناميكي (تماما مثل عنوان تنفيذ الوظيفة الافتراضية معروفة فقط عندما يعرف النوع الديناميكي).

سيتم تحديد موقع Subobject في وقت التشغيل مع VPTR و VTable (إعادة استخدام VPTR الحالي يتضمن علما أقل من الفضاء)، أو مؤشر داخلي مباشر إلى subobject (المزيد من النفقات العامة، أقل حاجة إليها).

نظرا لأن إزاحة فئة قاعدة افتراضية يتم تحديدها فقط للحصول على كائن كامل، ولا يمكن أن تكون معروفة لنوع معلن معين، لا يمكن تخصيص قاعدة افتراضية في Offset Zero ولا توجد قاعدة أساسية. وبعد لن تقوم فئة مشتقة بإعادة استخدام 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 ومن المعروف وكذلك هو الموضع النسبي من subobject معروف في وقت الترجمة. لكن هنا:

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 تسعى لتحديد سلوك الوصل الافتراضي الوحيد، و vBottom التعريف هو خطأ لعدم التجاوز المشترك.

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

تطبيق:

تشمل بناء الطبقة ذات الطبقات الأساسية غير الافتراضية مع فئات أساسية غير افتراضية لاستدعاء منشئين من الفئة الأساسية بنفس الترتيب الذي تم إنشاؤه لمتغيرات الأعضاء، وتغيير النوع الديناميكي في كل مرة ندخل فيها CTTOR. أثناء الإنشاء، تتصدر الفئة الأساسية حقا كما لو كانت كائنات كاملة (هذا صحيح للغاية مع تماثيل فئة قاعدة كاملة مستحيلة من المستحيل: فهي كائنات مع وظائف افتراضية غير محددة (نقية)). يمكن استدعاء الوظائف الافتراضية و RTTI أثناء الإنشاء (باستثناء الدالات الافتراضية الخالصة).

بناء فئة مع فصول أساسية غير افتراضية مع القواعد الافتراضية هي أكثر تعقيدا: أثناء الإنشاء، النوع الديناميكي هو نوع الطبقة الأساسية، ولكن تخطيط القاعدة الافتراضية لا يزال تخطيط النوع الأكثر مشتقا التي لم يتم بناؤها بعد، لذلك نحتاج إلى المزيد من vtables لوصف هذه الحالة:

// 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); 

أو مع هذا المؤشر في المنشئ:

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-INT يجب التحقق بعناية. بعد تهيئة جميع الأعضاء، this يمكن تمريرها إلى وظائف أخرى وتسجيلها في بعض المجموعة (حتى يبدأ الدمار).

ما هو أقل وضوحا هو أنه عند بناء فئة تنطوي على قواعد افتراضية مشتركة، توقف Subobjects عن إنشاء: أثناء بناء أ vBottom:

  • أولا يتم بناء القواعد الافتراضية: متى Top شيدت، يتم بناؤها مثل موضوع طبيعي (Top لا أعرف حتى أنه قاعدة افتراضية)

  • ثم يتم بناء الفصول الأساسية في اليسار إلى الترتيب الصحيح: vLeft تم بناء subobject ويصبح وظيفيا كطبيعي vLeft (ولكن مع vBottom تخطيط)، لذلك Top الطبقة الأساسية subobject الآن لديه vLeft نوع ديناميكي

  • ال vRight يبدأ البناء Subobject، والنوع الديناميكي من التغييرات الفئة الأساسية إلى VRIGHT؛ لكن vRight غير مشتق من vLeft, ، لا يعرف شيئا عنه vLeft, ، لذلك vLeft قاعدة مكسورة الآن؛

  • عندما جسم Bottom يبدأ المنشئ، وأنواع جميع subobjects استقرت و vLeft هو وظيفي مرة أخرى.

لست متأكدا من كيفية اتخاذ هذه الإجابة كإجابة كاملة دون ذكر البتات المحاذاة أو الحشو.

اسمحوا لي أن أعطي خلفية بعض المحاذاة:

"عنوان ذاكرة A، يقال إنه محاذاة N-byte عندما يكون مضاعفا من بايت N (حيث n هو قوة 2). في هذا السياق، فإن البايت هو أصغر وحدة وصول الذاكرة، أي كل عنوان ذاكرة يحدد بايت مختلفة. سيكون عنوان محاذاة N-BYTE لديه سجل LOG2 (ن) الأقل الأزرار ذات الأهمية الأقل عند التعبير عنها في ثنائي.

يعين محاذاة صياغة البديل البديل عنوان محاذاة AB / 8 BYTE (EX. محاذاة 64 بت محاذاة 8 بايت).

يقال إن الوصول إلى الذاكرة محاذاة عند الوصول إلى Datum هو N BYTES لفترة طويلة وعنوان Datum هو محاذاة N-BYTE. عندما لا يتم محاذاة الوصول إلى الذاكرة، يقال إنه غير محمول. لاحظ أنه بحلول تعريف الوصول إلى الذاكرة بايت بايت محاذاة دائما.

يقال إن مؤشر الذاكرة الذي يشير إلى البيانات البدائية التي هي NAY BYTES يجب أن تتماشى إذا سمح لها فقط بتوصيل عناوين n-byte المحاذاة، وإلا فإنه يقال إنه غير إجمالي. يتماشى مؤشر الذاكرة الذي يشير إلى إجمالي البيانات (بنية بيانات أو صفيف) إذا كان (وفقط إذا) يتم محاذاة كل مسند بدائي في المجاميع.

لاحظ أن التعاريف المذكورة أعلاه تفترض أن كل مسند بدائي هو قوة اثنين بايت طويلة. عندما يكون هذا هو الحال (كما هو الحال مع النقطة العائمة 80 بت على X86) يؤثر السياق على الشروط التي يعتبر فيها Datum محاذاة أم لا.

يمكن تخزين هياكل البيانات في الذاكرة على المكدس مع حجم ثابت يعرف على النحو المحدد أو على كومة مع حجم ديناميكي يعرف باسم غير محدود. "- من Wiki ...

للحفاظ على المحاذاة، يقوم برنامج التحويل البرمجي بإدراج بت الحشو في التعليمات البرمجية المترجمة لكائن هيكل / فئة. "على الرغم من أن المترجم (أو المترجم) يخصص عادة عناصر البيانات الفردية على حدود المحاذاة، فإن هياكل البيانات غالبا ما يكون لها أعضاء مع متطلبات محاذاة مختلفة. للحفاظ على المحاذاة المناسبة، يقوم المترجم عادة بإدراج أعضاء بيانات غير مثبت إضافي حتى يتم محاذاة كل عضو بشكل صحيح. بالإضافة إلى ذلك قد تكون مبطن بنية البيانات ككل مع عضو نهائي Unnamed. يسمح ذلك لكل عضو في مجموعة من الهياكل التي سيتم محاذاةها بشكل صحيح. .... ....

يتم إدراج الحشو فقط عندما يتبع عضو هيكل عضوا بمتطلبات محاذاة أكبر أو في نهاية الهيكل "- Wiki

للحصول على مزيد من المعلومات حول كيفية قيام دول مجلس التعاون الخليجي بذلك، يرجى إلقاء نظرة على

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

والبحث عن النص "محاذاة أساسية"

الآن دعنا نأتي لهذه المشكلة:

باستخدام فئة المثال، قمت بإنشاء هذا البرنامج لمجموعة التحويل البرمجي GCC يعمل على أوبونتو 64 بت.

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 of A) و Int. ستكون المؤشر طوله 8 بايت، وستكون INT طوله 4 بايت. وبالتالي قبل ترجمة الحجم هو 12 بايت. لكن المترجم سيضيف 4 بايت إضافية في نهاية INT A Cas Bits Medding. وبالتالي بعد تجميع، سيكون حجم كائنات A 12 + 4 = 16.

وبالمثل لكائنات الفئة ب.

سيكون الآن كائن C VPTRS (واحد لكل فئة A & Class B) و 3 Ints (A، B، C). لذلك يجب أن يكون الحجم 8 (vptr a) + 4 (int a) + 4 (bytes الحشو) + 8 (vptr b) + 4 (int b) + 4 (int c) = 32 bytes. لذلك سيكون الحجم الكلي ل C 32 بايت.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top