إدارة C++ الكائنات في منطقة عازلة ، بالنظر إلى محاذاة تخطيط الذاكرة الافتراضات

StackOverflow https://stackoverflow.com/questions/417446

سؤال

انا تخزين الكائنات في منطقة عازلة.الآن وأنا أعلم أنني لا يمكن أن تجعل الافتراضات حول تخطيط الذاكرة للكائن.

لو كنت أعرف الحجم الكلي للكائن هو acceptible إلى إنشاء مؤشر إلى هذه الذاكرة استدعاء وظائف على ذلك ؟

على سبيل المثالأقول لقد الفئة التالية:

[int,int,int,int,char,padding*3bytes,unsigned short int*]

1) إذا كنت تعرف هذه الفئة أن تكون من الحجم 24 و أعرف عنوان حيث يبدأ في الذاكرة في حين أنه ليس من الأسلم أن نفترض تخطيط الذاكرة هو acceptible أن يلقي هذا المؤشر و استدعاء وظائف على هذا الكائن الذي الوصول إلى هذه الأعضاء ؟ (لا c++ تعرف من قبل بعض السحر الموقف الصحيح من عضو؟)

2) إذا كان هذا غير آمنة/حسنا, هل هناك أي طريقة أخرى بخلاف باستخدام منشئ الذي يأخذ كل الحجج و سحب كل حجة للخروج من المخزن المؤقت في آن واحد ؟

تحرير:تم تغيير العنوان لجعله أكثر ملاءمة ما أنا أسأل.

هل كانت مفيدة؟

المحلول

ويمكنك إنشاء منشئ التي تأخذ جميع أعضاء ويعين لهم، ثم استخدم وضع الجديد.

class Foo
{
    int a;int b;int c;int d;char e;unsigned short int*f;
public:
    Foo(int A,int B,int C,int D,char E,unsigned short int*F) : a(A), b(B), c(C), d(D), e(E), f(F) {}
};

...
char *buf  = new char[sizeof(Foo)];   //pre-allocated buffer
Foo *f = new (buf) Foo(a,b,c,d,e,f);

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

والأساليب الفردية على المؤشر this ترتبط بشكل ثابت وهي ببساطة دعوة مباشرة إلى وظيفة مع this يجري المعلمة الأولى قبل المعلمات واضحة.

يتم الرجوع إليها

والمتغيرات الأعضاء باستخدام إزاحة من مؤشر this. إذا وضعت كائن من هذا القبيل:

0: vtable
4: a
8: b
12: c
etc...

وسيتم الوصول إليها من قبل a dereferencing this + 4 bytes.

نصائح أخرى

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

وهذا يبدو وكأنه كنت تريد نسخة C ++ من جاوة التسلسل / إلغاء التسلسل. هناك على الأرجح مكتبة هناك للقيام بذلك.

ترتبط

والمكالمات وظيفة غير افتراضية مباشرة مثل وظيفة C. يتم تمرير الكائن (هذا) المؤشر كما الوسيطة الأولى. لا يلزم معرفة تخطيط الكائن لاستدعاء الدالة.

يبدو أنك لا تخزين الكائنات نفسها في منطقة عازلة ، بل من هم المؤلفة.

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

إذا كان لديك فئة ليست جراب ، ثم في الذاكرة تخطيط الميادين ليست مضمونة ، و يجب أن لا تعتمد على أي لاحظ الطلب ، كما يسمح للتغيير في كل ترجمة.

ومع ذلك ، يمكنك تهيئة غير جراب مع البيانات من جراب.

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

class Foo{
   int a;
   int b;

public:
   void DoSomething(int x);
};

void Foo::DoSomething(int x){a = x * 2; b = x + a;}

int main(){
    Foo f;
    f.DoSomething(42);
    return 0;
}

المحول البرمجي بإنشاء رمز أن يفعل شيئا مثل هذا:

  1. وظيفة main:
    1. تخصيص 8 بايت على كومة لكائن "f"
    2. الاتصال الافتراضي مهيئ لفئة "Foo"(لا يفعل شيئا في هذه الحالة)
    3. دفع قيمة الوسيطة 42 على المكدس
    4. دفع مؤشر إلى كائن "f"على المكدس
    5. إجراء مكالمة إلى وظيفة Foo_i_DoSomething@4 (الاسم الفعلي هو عادة أكثر تعقيدا)
    6. تحميل إرجاع القيمة 0 في المجمع التسجيل
    7. العودة إلى المتصل
  2. وظيفة Foo_i_DoSomething@4 (في مكان آخر في مقطع التعليمات البرمجية)
    1. تحميل "x"قيمة من المكدس (ضغطت على حسب المتصل)
    2. ضرب من قبل 2
    3. تحميل "this"مؤشر من المكدس (ضغطت على حسب المتصل)
    4. حساب تعويض من الميدان "a"في غضون Foo وجوه
    5. إضافة حساب الإزاحة this مؤشر تحميلها في الخطوة 3
    6. تخزين المنتج ، المحسوبة في الخطوة 2 إلى تعويض المحسوبة في الخطوة 5
    7. تحميل "x"قيمة من المكدس مرة أخرى
    8. تحميل "this"مؤشر من كومة مرة أخرى
    9. حساب تعويض من الميدان "a"في غضون Foo الكائن مرة أخرى
    10. إضافة حساب الإزاحة this مؤشر تحميلها في الخطوة 8
    11. تحميل "a"القيمة المخزنة في تعويض ،
    12. إضافة "a"قيمة, تحميل الباحث الخطوة 12،"x"القيمة التي تم تحميلها في الخطوة 7
    13. تحميل "this"مؤشر من كومة مرة أخرى
    14. حساب تعويض من الميدان "b"في غضون Foo وجوه
    15. إضافة حساب الإزاحة this مؤشر تحميلها في الخطوة 14
    16. متجر المبلغ المحسوب في الخطوة 13, لتعويض المحسوبة في الخطوة 16
    17. العودة إلى المتصل

وبعبارة أخرى ، فإنه سيكون أكثر أو أقل نفس القانون كما لو كنت قد كتبت هذا (تفاصيل مثل اسم DoSomething وظيفة و طريقة تمرير this مؤشر تصل إلى المترجم):

class Foo{
    int a;
    int b;

    friend void Foo_DoSomething(Foo *f, int x);
};

void Foo_DoSomething(Foo *f, int x){
    f->a = x * 2;
    f->b = x + f->a;
}

int main(){
    Foo f;
    Foo_DoSomething(&f, 42);
    return 0;
}
  1. الكائن وجود جراب نوع ، في هذه الحالة ، يتم إنشاؤها بالفعل (ما إذا كنت استدعاء جديد.تخصيص التخزين المطلوبة بالفعل يكفي) ، يمكنك الوصول إلى أعضاء ، بما في ذلك استدعاء دالة على ذلك الكائن.ولكن هذا سوف تعمل فقط إذا كنت أعرف على وجه التحديد المواءمة المطلوبة بين T و حجم T (المخزن المؤقت قد لا تكون أصغر من ذلك) ، ومواءمة جميع أعضاء T.حتى بالنسبة جراب نوع المحول البرمجي يسمح لوضع الحشو بايت بين الأعضاء إذا كان يريد.غير جراب أنواع, هل يمكن أن يكون نفس الحظ إذا كان لديك نوع لا يوجد لديه وظائف افتراضية أو فئات أساسية لا يحددها المستخدم منشئ (بالطبع) وهذا ينطبق على القاعدة و كل غير أعضاء ثابتة أيضا.

  2. لجميع أنواع أخرى ، كل الرهانات.لديك لقراءة القيم أولا مع جراب ، ومن ثم تهيئة غير جراب نوع مع تلك البيانات.

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

وهذا مقبول إلى حد أن استخدام يلقي مقبول:

#include <iostream>

namespace {
    class A {
        int i;
        int j;
    public:
        int value()
        {
            return i + j;
        }
    };
}

int main()
{
    char buffer[] = { 1, 2 };
    std::cout << reinterpret_cast<A*>(buffer)->value() << '\n';
}

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

أقول لقد الفئة التالية:...

إذا كنت تعرف هذه الفئة أن تكون من الحجم 24 و أعرف عنوان حيث يبدأ في الذاكرة ...

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

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

وعندما نسخ الكائنات تحتاج إلى تأكد من أنك بشكل صحيح التعامل مع المؤشرات.

كنت قد تكون مهتمة أيضا في أشياء مثل جوجل البروتوكول مخازن أو Facebook هو التوفير.


نعم هذه القضايا الصعبة.و, نعم, بعض لغات البرمجة اكتساح لهم تحت البساط. ولكن هناك الكثير من الاشياء الحصول على اجتاحت تحت البساط:

في الشمس الساخنة JVM, تخزين الكائن هو الانحياز إلى أقرب حدود 64 بت.علاوة على ذلك, كل كائن لديه 2-كلمة رأس في الذاكرة.JVM كلمة حجم عادة المنبر الأصلي مؤشر الحجم.(كائن يتكون من 32 بت الباحث و 64 بت مزدوجة -- 96 بت من البيانات -- سيتطلب) كلمتين لكائن رأس واحد كلمة int, كلمتين مزدوجة.5 الكلمات:160 بت.بسبب المحاذاة ، هذا الكائن سوف تحتل 192 بت من الذاكرة.

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

تكتيك آخر للذاكرة محاذاة يمكن استعادة بعض من هذه المساحة.

  1. إذا كانت فئة لا يحتوي على وظائف افتراضية (وبالتالي فئة الحالات لا vptr), و إذا كنت جعل الصحيح افتراضات حول الطريقة التي class' عضو البيانات المنصوص عليها في الذاكرة ، ثم تفعل ما تقترحه قد عمل (ولكن قد لا تكون محمولة).
  2. نعم هناك طريقة أخرى (أكثر الاصطلاحية ولكن ليس أكثر أمانا ...كنت لا تزال بحاجة إلى معرفة كيفية الطبقة يحدد البيانات) استخدام ما يسمى "وضع المشغل الجديد" و منشئ افتراضي.

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

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

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