سؤال

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

كانت تجربتي الوحيدة مع مجموعة Garbage على بضع أجهزة كمبيوتر رخيصة في الثمانينات من القرن الماضي ، حيث كان هذا يعني أن النظام سيتجمد لبضع ثوانٍ في كثير من الأحيان. أنا متأكد من أنه تحسن منذ ذلك الحين ، ولكن كما يمكنك أن تخمن ، فإن هذا لم يتركني برأي كبير.

ما هي المزايا التي يمكن أن تقدمها جمع القمامة مطور C ++ ذي الخبرة؟

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

المحلول

ما زلت أسمع الناس الذين يشكون من أن C ++ ليس لديه جمع القمامة.

أنا آسف جدا لهم. بشكل جاد.

C ++ لديه Raii ، وأنا دائما أشكو من العثور على أي Raii (أو Raii مخصصة) في اللغات التي تم جمعها القمامة.

ما هي المزايا التي يمكن أن تقدمها جمع القمامة مطور C ++ ذي الخبرة؟

أداة أخرى.

لقد كتبها مات جمجموعة القمامة في C ++ - لماذا؟): لا نحتاج إلى ميزات C ++ حيث يمكن ترميز معظمها في C ، ولا نحتاج إلى ميزات C لأن معظمها يمكن أن يشفر في التجميع ، وما إلى ذلك .. يجب أن تتطور C ++.

كمطور: لا يهمني GC. حاولت كل من Raii و GC ، وأجد Raii متفوقة بشكل كبير. كما قال جريج روجرز في منصبه (مجموعة القمامة في C ++ - لماذا؟) ، تسرب الذاكرة ليست فظيعة للغاية (على الأقل في C ++ ، حيث تكون نادرة إذا تم استخدام C ++ بالفعل) لتبرير GC بدلاً من RAII. لدى GC التخصيص/الانتهاء من GC وهو مجرد وسيلة ل اكتب رمزًا لا يهتم بخيارات ذاكرة محددة.

هذه الجملة الأخيرة مهمة: من المهم كتابة رمز أن "Juste لا تهتم". بالطريقة نفسها في C ++ Raii ، لا نهتم بتحرير Ressource لأن Raii تفعل ذلك من أجلنا ، أو لتهيئة الكائنات لأن المنشئ يفعل ذلك من أجلنا ، من المهم في بعض الأحيان فقط الترميز دون الاهتمام بمن هو مالك الذاكرة ، وأي نوع مؤشر (مشترك ، ضعيف ، وما إلى ذلك) نحتاج إلى هذا أو هذا الجزء من الكود. يبدو أن هناك حاجة إلى GC في C ++. (حتى لو كنت شخصيًا فشل في رؤيته)

مثال على استخدام GC الجيد في C ++

في بعض الأحيان ، في التطبيق ، لديك "بيانات عائمة". تخيل بنية تشبه الأشجار للبيانات ، لكن لا أحد "مالك" للبيانات (ولا أحد يهتم حقًا بموعد تدميرها بالضبط). يمكن للكائنات المتعددة استخدامه ، ثم تجاهله. تريد أن يتم تحريره عندما لا يستخدمه أحد بعد الآن.

نهج C ++ هو استخدام مؤشر ذكي. تعزز :: shared_ptr يتبادر إلى الذهن. لذلك كل قطعة من البيانات مملوكة بمؤشر مشترك خاص بها. رائع. المشكلة هي أنه عندما يمكن أن تشير كل جزء من البيانات إلى جزء آخر من البيانات. لا يمكنك استخدام المؤشرات المشتركة لأنها تستخدم عداد مرجعي ، والتي لن تدعم المراجع الدائرية (نقاط إلى B و B إلى أ). لذلك يجب أن تعرف أن تفكر كثيرًا في مكان استخدام المؤشرات الضعيفة (Boost :: Pream_ptr) ، ومتى تستخدم المؤشرات المشتركة.

مع GC ، يمكنك فقط استخدام البيانات المنظمة للشجرة.

الجانب السلبي الذي يجب ألا تهتم به متى سيتم تدمير "البيانات العائمة" حقًا. فقط ذلك سوف يكون دمر.

استنتاج

لذلك في النهاية ، إذا تم القيام به بشكل صحيح ، وتوافق مع التعبيرات الحالية لـ C ++ ، فإن GC ستكون ملف أداة جيدة أخرى لـ C ++.

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

نصائح أخرى

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

يبدو هذا السؤال مشابهًا لـ "ما الذي يجب على C ++ تقديمه لمطور التجميع ذي الخبرة؟ التعليمات والتواصل الفرعي القضاء على الحاجة إليها ، أليس كذلك؟"

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

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

يبدو أن العامل المحفز لدعم GC في C ++ هو برمجة Lambda ، وظائف مجهولة. ستكون الفائدة للمطورين العاديين أبسط وأكثر موثوقية وأسرع لمكتبات Lambda.

يساعد GC أيضًا في محاكاة الذاكرة اللانهائية ؛ السبب الوحيد الذي تجعلك بحاجة إلى حذف القرون هو أنك تحتاج إلى إعادة تدوير الذاكرة. إذا كان لديك إما GC أو الذاكرة اللانهائية ، فليس هناك حاجة لحذف القرون بعد الآن.

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

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

ما هي المزايا التي يمكن أن تقدمها جمع القمامة مطور C ++ ذي الخبرة؟

عدم الاضطرار إلى مطاردة تسرب الموارد في رمز زملائك الأقل خبرة.

لا أفهم كيف يمكن للمرء أن يجادل بأن RAII يحل محل GC ، أو أنه متفوق إلى حد كبير. هناك العديد من الحالات التي تعالجها GC والتي لا يمكن لـ RAII ببساطة التعامل معها على الإطلاق. هم وحوش مختلفة.

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

فيما يلي مثال بسيط حيث لا يمكن لك أي auto_ptr أو raii مساعدتك:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <memory>

using namespace std;

volatile sig_atomic_t got_sigint = 0;

class A {
        public:
                A() { printf("ctor\n"); };
                ~A() { printf("dtor\n"); };
};

void catch_sigint (int sig)
{
        got_sigint = 1;
}

/* Emulate expensive computation */
void do_something()
{
        sleep(3);
}

void handle_sigint()
{
        printf("Caught SIGINT\n");
        exit(EXIT_FAILURE);
}

int main (void)
{
        A a;
        auto_ptr<A> aa(new A);

        signal(SIGINT, catch_sigint);

        while (1) {
                if (got_sigint == 0) {
                        do_something();
                } else {
                        handle_sigint();
                        return -1;
                }
        }
}

لن يتم استدعاء المدمرة من A. بالطبع ، إنه مثال اصطناعي ومتحمس إلى حد ما ، ولكن يمكن أن يحدث وضع مماثل بالفعل ؛ على سبيل المثال ، عندما يتم استدعاء الكود الخاص بك بواسطة رمز آخر يتولى سيغنت والذي لا تتحكم فيه على الإطلاق (مثال ملموس: امتدادات MEX في MATLAB). هذا هو نفس السبب وراء عدم ضمان تنفيذ شيء ما في Python. يمكن أن يساعدك GC في هذه الحالة.

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

يمكن أن يكون أيضًا أسرع بكثير لاستخدام GC ، لنفس الأسباب: إذا كنت بحاجة إلى تخصيص/تخصيص العديد من الكائنات (خاصة الكائنات الصغيرة) ، فإن GC ستتفوق بشكل كبير على Raii ، إلا إذا كنت تكتب مخصصًا مخصصًا ، حيث يمكن لـ GC تخصيص/ تنظيف العديد من الأشياء في ممر واحد. تستخدم بعض مشاريع C ++ المعروفة GC ، حتى في حالة أهمية الأداء (انظر على سبيل المثال تيم سويني حول استخدام GC في بطولة Unreal: http://lambda-the-ultimate.org/node/1277). GC يزيد أساسا من الإنتاجية على حساب الكمون.

بالطبع ، هناك حالات يكون فيها RAII أفضل من GC ؛ على وجه الخصوص ، يهتم مفهوم GC في الغالب بالذاكرة ، وهذا ليس هو Ressource الوحيد. أشياء مثل الملف ، إلخ ... يمكن التعامل معها بشكل جيد مع Raii. اللغات التي لا تتناول الذاكرة مثل Python أو Ruby لديها شيء مثل Raii لتلك الحالات ، راجع للشغل (مع بيان في Python). يعد Raii مفيدًا جدًا عندما تحتاج بدقة إلى التحكم عند تحرير Ressource ، وهذا هو الحال في كثير من الأحيان بالنسبة للملفات أو الأقفال على سبيل المثال.

إنه خطأ شامل في الافتراض أنه نظرًا لأن C ++ ليس لديه مجموعة من القمامة مخبوز في اللغة, ، لا يمكنك استخدام جمع القمامة في فترة C ++. هذا غير منطقي. أعرف المبرمجين Elite C ++ الذين يستخدمون جامع Boehm كمسألة بالطبع في عملهم.

مجموعة القمامة تسمح بذلك يؤجل القرار بشأن من يمتلك شيء.

يستخدم C ++ دلالات القيمة ، لذلك مع RAII ، في الواقع ، يتم إعادة الكائنات عند الخروج من النطاق. يشار إلى هذا أحيانًا باسم "GC فوري".

عندما يبدأ برنامجك في استخدام-مرجعية) (من خلال المؤشرات الذكية وما إلى ذلك ...) ، فإن اللغة لم تعد تدعمك ، فأنت تترك في مكتبة المؤشر الذكي.

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

مجموعة القمامة تجعل RCU التزامن بدون قفل أسهل بكثير في التنفيذ بشكل صحيح وكفاءة.

سلامة الخيط أسهل وقابلية التوسع

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

جمع القمامة هو حقا أساس لإدارة الموارد التلقائية. وجعل GC يغير الطريقة التي تتعامل بها مع المشكلات بطريقة يصعب تحديدها. على سبيل المثال عندما تقوم بإدارة الموارد اليدوية ، تحتاج إلى:

  • ضع في اعتبارك متى يمكن تحرير عنصر (هل يتم الانتهاء من جميع الوحدات/الفصول الدراسية؟)
  • فكر في من مسؤولية تحرير مورد عندما يكون جاهزًا للتحرير (أي فئة/وحدة يجب أن تحرر هذا العنصر؟)

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

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

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


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

أنا أيضًا لدي شكوك في أن C ++ Commitee يضيف مجموعة قمامة كاملة إلى المعيار.

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

باستخدام RAII مع المؤشرات الذكية يلغي الحاجة إليها ، أليس كذلك؟

يمكن استخدام المؤشرات الذكية لتنفيذ العد المرجعي في C ++ وهو شكل من أشكال جمع القمامة (إدارة الذاكرة الأوتوماتيكية) ولكن الإنتاج لم تعد تستخدم العد المرجعي لأن لديها بعض أوجه القصور المهمة:

  1. مرجع العد تسرب دورات. ضع في اعتبارك A↔B ، يشير كلا الكائنين A و B إلى بعضهما البعض بحيث يكون لهما عدد مرجعي من 1 ولم يتم جمعهما ولكن يجب استردادهما. خوارزميات متقدمة مثل حذف المحاكمة حل هذه المشكلة ولكن أضف الكثير من التعقيد. استخدام weak_ptr كحل بديل يعود إلى إدارة الذاكرة اليدوية.

  2. العد المرجعي الساذج بطيء لعدة أسباب. أولاً ، يتطلب الأمر أن تصطدم بالتعدادات المرجعية خارج المخططات الخارجة (انظر Boost's shared_ptr ما يصل إلى 10 × أبطأ من مجموعة القمامة OCAML). ثانياً ، يمكن للمدمرين الذين تم حقنهم في نهاية النطاق أن يتحملوا مكالمات الوظائف الافتراضية غير الضرورية والتوسعة وتمنع التحسينات مثل التخلص من استدعاء الذيل.

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

ما هي المزايا التي يمكن أن تقدمها جمع القمامة مطور C ++ ذي الخبرة؟

الإنتاجية والموثوقية هي الفوائد الرئيسية. بالنسبة للعديد من التطبيقات ، تتطلب إدارة الذاكرة اليدوية جهود المبرمج المهمة. من خلال محاكاة آلة الذاكرة اللانهائية ، تحرر مجموعة Garbage المبرمج من هذا العبء الذي يسمح لهم بالتركيز على حل المشكلات وتهرب من بعض الفئات المهمة من الأخطاء (المؤشرات المتدلية ، مفقودة free, ، مزدوج free). علاوة على ذلك ، يسهل جمع القمامة أشكالًا أخرى من البرمجة ، على سبيل المثال عن طريق حل مشكلة Funarg للأعلى (1970).

في إطار يدعم GC ، قد يتم تمرير إشارة إلى كائن ثابت مثل السلسلة بنفس طريقة بدائية. النظر في الفصل (C# أو Java):

public class MaximumItemFinder
{
  String maxItemName = "";
  int maxItemValue = -2147483647 - 1;

  public void AddAnother(int itemValue, String itemName)
  {
    if (itemValue >= maxItemValue)
    {
      maxItemValue = itemValue;
      maxItemName = itemName;
    }
  }
  public String getMaxItemName() { return maxItemName; }
  public int getMaxItemValue() { return maxItemValue; }
}

لاحظ أن هذا الرمز يجب ألا يفعل أي شيء مع محتويات من أي من الأوتار ، ويمكن ببساطة التعامل معها على أنها بدائية. بيان مثل maxItemName = itemName; من المحتمل أن تنشئ تعليمين: تحميل سجل متبوعًا بمتجر تسجيل. ال MaximumItemFinder لن يكون لديه أي وسيلة لمعرفة ما إذا كان المتصلين AddAnother ستحتفظ بأي إشارة إلى الأوتار التي تم تمريرها ، ولن يكون لدى المتصلين أي وسيلة لمعرفة المدة MaximumItemFinder سوف تحتفظ بالإشارات إليهم. المتصلين getMaxItemName لن يكون لديه أي وسيلة لمعرفة ما إذا ومتى MaximumItemFinder وقد تخلى المورد الأصلي للسلسلة التي تم إرجاعها عن جميع الإشارات إليها. لأن الكود يمكن أن يمر ببساطة مراجع السلسلة حول القيم البدائية ، ومع ذلك ، لا شيء من هذه الأشياء مهم.

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

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

مجموعة القمامة يمكن أن تجعل التسريبات أسوأ كابوس لك

GC الكامل التي تتعامل مع أشياء مثل المراجع الدورية ستكون إلى حد ما ترقية على مرفوض shared_ptr. أرحب به إلى حد ما في C ++ ، ولكن ليس على مستوى اللغة.

واحدة من الجمال حول C ++ هو أنها لا تجبر مجموعة القمامة عليك.

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

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

تسببت مجموعة القمامة جنبًا إلى جنب مع تعاون فريق أقل من مثالي في أسوأ وأصعب تسربات في حالتنا.

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

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

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

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

بدون جمع القمامة ، مطور C من شأنه أن يخلق أ مؤشر متدلي. قد يحاول الوصول إليه في مرحلة ما ويتسبب في تعطل البرنامج. الآن هذا خطأ اختبار/مرئي للمستخدم. C يصبح محرجا قليلا ويصحح خطأه. في سيناريو GC ، قد تكون مجرد محاولة معرفة المكان الذي يتسرب فيه النظام أمرًا صعبًا لدرجة أن بعض التسريبات لم يتم تصحيحها أبدًا. هذه ليست valgrind-تسرب مادي من النوع الذي يمكن اكتشافه بسهولة وتحديده إلى سطر معين من التعليمات البرمجية.

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

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

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

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

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