سؤال

ما هي بعض النصائح العامة للتأكد من عدم تسرب الذاكرة في برامج C++؟كيف يمكنني معرفة من الذي يجب عليه تحرير الذاكرة التي تم تخصيصها ديناميكيًا؟

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

المحلول

بدلاً من إدارة الذاكرة يدويًا، حاول استخدام المؤشرات الذكية حيثما أمكن ذلك.
نلقي نظرة على تعزيز ليب, TR1, ، و مؤشرات ذكية.
كما أصبحت المؤشرات الذكية الآن جزءًا من معيار C++ المسمى سي++11.

نصائح أخرى

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

لا تكتب

Object* x = new Object;

او حتى

shared_ptr<Object> x(new Object);

عندما يمكنك الكتابة فقط

Object x;

يستخدم رايي

  • ننسى جمع القمامة (استخدم RAII بدلاً من ذلك).لاحظ أنه حتى Garbage Collector يمكن أن يتسرب أيضًا (إذا نسيت "إلغاء" بعض المراجع في Java/C#)، وأن Garbage Collector لن يساعدك في التخلص من الموارد (إذا كان لديك كائن حصل على مؤشر لـ ملفًا، فلن يتم تحرير الملف تلقائيًا عندما يخرج الكائن عن النطاق إذا لم تقم بذلك يدويًا في Java، أو تستخدم نمط "التخلص" في C#).
  • انسَ قاعدة "إرجاع واحد لكل دالة"..هذه نصيحة جيدة في لغة C لتجنب التسريبات، لكنها أصبحت قديمة في لغة C++ بسبب استخدامها للاستثناءات (استخدم RAII بدلاً من ذلك).
  • و في حين "نمط الساندويتش" هي نصيحة C جيدة، ذلك قديم في C++ بسبب استخدامه للاستثناءات (استخدم RAII بدلاً من ذلك).

يبدو أن هذا المنشور متكرر، ولكن في لغة C++، فإن النمط الأساسي الذي يجب معرفته هو رايي.

تعلم كيفية استخدام المؤشرات الذكية، سواء من التعزيز أو TR1 أو حتى من auto_ptr المتواضع (ولكنه فعال في كثير من الأحيان بما فيه الكفاية) (ولكن يجب أن تعرف حدوده).

RAII هو أساس كل من أمان الاستثناءات والتخلص من الموارد في لغة C++، ولن يمنحك أي نمط آخر (ساندويتش، وما إلى ذلك) كليهما (وفي معظم الأوقات، لن يمنحك أيًا منهما).

انظر أدناه مقارنة بين كود RAII ورمز غير RAII:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

عن رايي

للتلخيص (بعد التعليق من الغول مزمور 33)، يعتمد RAII على ثلاثة مفاهيم:

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

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

كمطور، يعد هذا أمرًا قويًا جدًا بالفعل حيث لن تحتاج إلى الاهتمام بالتعامل اليدوي مع الموارد (كما هو الحال في لغة C، أو بالنسبة لبعض الكائنات في Java التي تستخدم بشكل مكثف try/finally لهذه الحالة)...

تحرير (2012/02/12)

"الكائنات ذات النطاق ...سيتم تدميرها...بغض النظر عن الخروج" هذا ليس صحيحا تماما.هناك طرق لخداع RAII.أي نكهة للإنهاء () ستتجاوز عملية التنظيف.الخروج (EXIT_SUCCESS) هو تناقض لفظي في هذا الصدد.

wilhelmtell

wilhelmtell محق تمامًا في ذلك:هناك استثنائي طرق غش RAII، كلها تؤدي إلى توقف العملية بشكل مفاجئ.

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

ولكن لا يزال يتعين علينا أن نعرف عن هذه الحالات، لأنها رغم أنها نادراً ما تحدث، إلا أنها لا تزال ممكنة الحدوث.

(من يدعو terminate أو exit في كود C++ غير رسمي؟...أتذكر أنني اضطررت للتعامل مع هذه المشكلة عند اللعب بها تخمة:هذه المكتبة موجهة جدًا للغة C، حيث تم تصميمها بشكل نشط لجعل الأمور صعبة على مطوري C++ الذين لا يهتمون بها كومة البيانات المخصصة, ، أو اتخاذ قرارات "مثيرة للاهتمام" بشأنها لا يعودون أبدًا من الحلقة الرئيسية...لن أعلق على ذلك).

سوف ترغب في إلقاء نظرة على المؤشرات الذكية، مثل مؤشرات التعزيز الذكية.

بدلاً من

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

سيتم حذف Boost::shared_ptr تلقائيًا بمجرد أن يصبح عدد المراجع صفرًا:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

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

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

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

كما هو الحال دائمًا، استخدم قبعة التفكير الخاصة بك مع أي أداة...

تقرأ على رايي وتأكد من أنك تفهم ذلك.

معظم حالات تسرب الذاكرة تكون نتيجة عدم الوضوح بشأن ملكية الكائن وعمره.

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

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

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

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

آه، أيها الأطفال الصغار وجامعو القمامة الجدد...

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

create a thing
use that thing
destroy that thing

في بعض الأحيان يكون من الضروري الإنشاء والتدمير في أماكن مختلفة على نطاق واسع؛أعتقد أنه من الصعب تجنب ذلك.

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

يتم تعريف العديد من المؤشرات الأخرى حسب الحاجة عندما يحتاج كيان إلى الوصول إلى كيان آخر، لمسح المصفوفات أو أي شيء آخر؛هؤلاء هم "مجرد النظر".بالنسبة لمثال المشهد ثلاثي الأبعاد - يستخدم الكائن نسيجًا ولكنه لا يمتلكه؛قد تستخدم الكائنات الأخرى نفس الملمس.تدمير كائن لا لا استدعاء تدمير أي القوام.

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

سؤال عظيم!

إذا كنت تستخدم لغة c++ وتقوم بتطوير تطبيق وحدة المعالجة المركزية والذاكرة في الوقت الفعلي (مثل الألعاب)، فستحتاج إلى كتابة مدير الذاكرة الخاص بك.

أعتقد أنه من الأفضل أن تقوم بدمج بعض الأعمال المثيرة للاهتمام لمؤلفين مختلفين، ويمكنني أن أقدم لك بعض التلميحات:

  • تمت مناقشة مخصص الحجم الثابت بشكل مكثف، في كل مكان في الشبكة

  • تم تقديم تخصيص الكائنات الصغيرة بواسطة Alexandrescu في عام 2001 في كتابه المثالي "Modern c++ design"

  • يمكن العثور على تقدم كبير (مع توزيع الكود المصدري) في مقالة مذهلة في Game Programming Gem 7 (2008) بعنوان "مخصص الكومة عالية الأداء" كتبها ديميتار لازاروف

  • يمكن العثور على قائمة كبيرة من الموارد في هذا شرط

لا تبدأ في كتابة مخصص غير مفيد بنفسك ...قم بتوثيق نفسك أولاً.

إحدى التقنيات التي أصبحت شائعة في إدارة الذاكرة في لغة C++ هي رايي.تستخدم بشكل أساسي المُنشئين/المدمرين للتعامل مع تخصيص الموارد.بالطبع هناك بعض التفاصيل البغيضة الأخرى في C++ بسبب السلامة الاستثنائية، ولكن الفكرة الأساسية بسيطة جدًا.

تتعلق المشكلة بشكل عام بمسألة الملكية.أوصي بشدة بقراءة سلسلة C++ الفعالة من تأليف Scott Meyers و Modern C++ Design من تأليف Andrei Alexandrescu.

هناك بالفعل الكثير حول كيفية عدم التسرب، ولكن إذا كنت بحاجة إلى أداة تساعدك على تتبع التسربات، فقم بإلقاء نظرة على:

مؤشرات المستخدم الذكية في كل مكان يمكنك!تختفي فئات كاملة من حالات تسرب الذاكرة.

شارك واعرف قواعد ملكية الذاكرة عبر مشروعك.يؤدي استخدام قواعد COM إلى تحقيق أفضل تناسق ([في] المعلمات مملوكة للمتصل، ويجب على المستدعى نسخها؛[out] المعلمات مملوكة للمتصل، ويجب على المستدعي عمل نسخة إذا احتفظ بمرجع؛إلخ.)

valgrind إنها أداة جيدة للتحقق من تسرب ذاكرة برامجك أثناء التشغيل أيضًا.

وهو متوفر على معظم إصدارات Linux (بما في ذلك Android) وعلى Darwin.

إذا كنت معتادًا على كتابة اختبارات الوحدة لبرامجك، فيجب أن تعتاد على تشغيل valgrind بشكل منهجي في الاختبارات.من المحتمل أن يتجنب العديد من حالات تسرب الذاكرة في مرحلة مبكرة.كما أنه عادةً ما يكون من الأسهل تحديدها في اختبارات بسيطة مقارنة بالبرنامج الكامل.

بالطبع تظل هذه النصيحة صالحة لأي أداة أخرى لفحص الذاكرة.

أيضًا، لا تستخدم الذاكرة المخصصة يدويًا إذا كانت هناك فئة مكتبة std (على سبيل المثال.المتجه).تأكد إذا انتهكت هذه القاعدة أن لديك أداة تدمير افتراضية.

إذا كنت لا تستطيع/لا تستخدم مؤشرًا ذكيًا لشيء ما (على الرغم من أن ذلك يجب أن يكون علامة حمراء كبيرة)، فاكتب الكود الخاص بك باستخدام:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

هذا واضح، ولكن تأكد من كتابته قبل قمت بكتابة أي رمز في النطاق

المصدر المتكرر لهذه الأخطاء هو عندما يكون لديك طريقة تقبل مرجعًا أو مؤشرًا لكائن ما ولكنها تترك الملكية غير واضحة.يمكن أن تجعل اصطلاحات الأسلوب والتعليق هذا الأمر أقل احتمالا.

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

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

لا تستخدم مراجع غير ثابتة في قوائم الوسائط.من غير الواضح جدًا عند قراءة رمز المتصل أن المستدعى قد احتفظ بمرجع إلى المعلمة.

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

النصائح حسب الأهمية:

-نصيحة رقم 1: تذكر دائمًا أن تعلن أن أدوات التدمير الخاصة بك "افتراضية".

-نصيحة رقم 2 استخدم RAII

-نصيحة رقم 3 استخدم المؤشرات الذكية الخاصة بالتعزيز

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

-نصيحة رقم 5 إذا كان العمل غير رسمي/غير مهم للأداء (كما هو الحال في الألعاب التي تحتوي على آلاف العناصر) فانظر إلى حاوية مؤشر التعزيز الخاصة بـ Thorsten Ottosen

-نصيحة رقم 6: ابحث عن رأس اكتشاف التسرب للنظام الأساسي الذي تختاره مثل رأس "vld" الخاص بكشف التسرب المرئي

إذا استطعت، استخدم Boost Shared_ptr وAuto_ptr القياسي C++.تلك تنقل دلالات الملكية.

عندما تقوم بإرجاع auto_ptr، فإنك تخبر المتصل أنك تمنحه ملكية الذاكرة.

عندما تقوم بإرجاع Shared_ptr، فإنك تخبر المتصل أن لديك إشارة إليه وأنه يشارك في الملكية، ولكنها ليست مسؤوليته فقط.

تنطبق هذه الدلالات أيضًا على المعلمات.إذا مرر لك المتصل auto_ptr، فهو يمنحك الملكية.

ذكر آخرون طرقًا لتجنب تسرب الذاكرة في المقام الأول (مثل المؤشرات الذكية).لكن أداة التصنيف وتحليل الذاكرة غالبًا ما تكون الطريقة الوحيدة لتتبع مشكلات الذاكرة بمجرد حدوثها.

التحقق من ذاكرة فالجريند هو واحد مجاني ممتاز.

بالنسبة لـ MSVC فقط، قم بإضافة ما يلي إلى أعلى كل ملف .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

بعد ذلك، عند تصحيح الأخطاء باستخدام VS2003 أو إصدار أحدث، سيتم إخبارك بأي تسربات عند خروج البرنامج (يتتبع الجديد/الحذف).إنها أساسية، لكنها ساعدتني في الماضي.

يعد valgrind (متاح فقط لمنصات *nix) أداة رائعة جدًا لفحص الذاكرة

إذا كنت ستقوم بإدارة ذاكرتك يدويًا، فلديك حالتان:

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

إذا كنت بحاجة إلى كسر أي من هذه القواعد، يرجى توثيق ذلك.

الأمر كله يتعلق بملكية المؤشر.

  • حاول تجنب تخصيص الكائنات ديناميكيًا.طالما أن الفئات لديها منشئات ومدمرات مناسبة، استخدم متغيرًا من نوع الفئة، وليس مؤشرًا لها، وستتجنب التخصيص الديناميكي وإلغاء التخصيص لأن المترجم سيقوم بذلك نيابةً عنك.
    في الواقع هذه أيضًا الآلية التي تستخدمها "المؤشرات الذكية" ويشار إليها باسم RAII من قبل بعض الكتاب الآخرين ؛-) .
  • عندما تقوم بتمرير كائنات إلى وظائف أخرى، قم بتفضيل المعلمات المرجعية على المؤشرات.وهذا يتجنب بعض الأخطاء المحتملة.
  • قم بتعريف المعلمات const، حيثما أمكن ذلك، وخاصة المؤشرات إلى الكائنات.بهذه الطريقة لا يمكن تحرير الكائنات "عن طريق الخطأ" (إلا إذا قمت بإلقاء الثوابت بعيدًا؛-))).
  • قلل عدد الأماكن في البرنامج التي تقوم فيها بتخصيص الذاكرة وإلغاء تخصيصها.ه.ز.إذا قمت بتخصيص أو تحرير نفس النوع عدة مرات، فاكتب دالة له (أو طريقة مصنع؛-)).
    بهذه الطريقة يمكنك إنشاء مخرجات تصحيح الأخطاء (العناوين التي تم تخصيصها وإلغاء تخصيصها،...) بسهولة، إذا لزم الأمر.
  • استخدم وظيفة المصنع لتخصيص كائنات من عدة فئات ذات صلة من وظيفة واحدة.
  • إذا كانت فصولك تحتوي على فئة أساسية مشتركة مع أداة تدمير افتراضية، فيمكنك تحريرها جميعًا باستخدام نفس الوظيفة (أو الطريقة الثابتة).
  • تحقق من برنامجك باستخدام أدوات مثل التنقية (للأسف الكثير من $/€/...).

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

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

على سبيل المثال تحقق في هذا موقع تصحيح تخصيص الذاكرة في C ++] ملاحظة:هناك خدعة لمشغل الحذف أيضًا مثل هذا:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

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

يمكنك أيضًا تجربة شيء مثل BoundsChecker ضمن Visual Studio وهو أمر مثير للاهتمام وسهل الاستخدام.

نقوم بتغليف جميع وظائف التخصيص الخاصة بنا بطبقة تُلحق سلسلة قصيرة في المقدمة وعلامة حارس في النهاية.على سبيل المثال، سيكون لديك اتصال بـ "myalloc( pszSomeString, iSize, iAlignment );أو new( "description, iSize ) MyObject();الذي يخصص الحجم المحدد داخليًا بالإضافة إلى مساحة كافية للرأس والحارس.بالطبع، لا تنسَ التعليق على هذا للإصدارات غير التصحيحية!يستغرق الأمر المزيد من الذاكرة للقيام بذلك ولكن الفوائد تفوق التكاليف بكثير.

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

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

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

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

هنا بدلا من ذلك وظيفة الموضوع

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

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

param.release();

يتم استدعاؤه في الوظيفة/الطريقة الرئيسية؟لا شئ!لأننا "سنخبر" auto_ptr بتجاهل إلغاء التخصيص.هل إدارة ذاكرة C++ سهلة، أليس كذلك؟هتافات،

إيما!

قم بإدارة الذاكرة بنفس الطريقة التي تدير بها الموارد الأخرى (المقابض، الملفات، اتصالات قاعدة البيانات، المقابس...).لن يساعدك GC معهم أيضًا.

عودة واحدة بالضبط من أي وظيفة.بهذه الطريقة يمكنك إلغاء التخصيص هناك وعدم تفويته أبدًا.

من السهل جدًا ارتكاب خطأ بخلاف ذلك:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top