متى يتم تحرير قيمة الحالة القابلة للتغيير من الكومة؟

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

سؤال

على F# WikiBook تحت تغليف الحالة المتغيرة القسم، هناك مقتطف التعليمات البرمجية التالي.

> let incr =
    let counter = ref 0
    fun () ->
        counter := !counter + 1
        !counter;;

val incr : (unit -> int)

> incr();;
val it : int = 1

> incr();;
val it : int = 2

> incr();;
val it : int = 3

في البداية، بدا الأمر سهلاً بما فيه الكفاية لتقبل حقيقة أنه قابل للتغيير counter زيادات القيمة في كل مرة incr يتم استدعاؤه.

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

إذن الأسئلة الرئيسية هي:

  • متى counter تحررت من الكومة؟
  • ليس كذلك counter تسرب الذاكرة؟
هل كانت مفيدة؟

المحلول

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

الآن، في هذا المثال تحديدًا، يبدو أن 'incr' هي دالة ذات مستوى أعلى، مما يعني أن قيمتها تستمر طوال عمر البرنامج (أو الجلسة التفاعلية في حالة الكتابة في fsi.exe).يمكنك أن تسمي هذا "تسريب"، لكن ذلك يعتمد على النية.إذا كان لديك عداد معرف فريد تحتاجه طوال عمر البرنامج بأكمله، فسيتعين عليك تخزين متغير العداد هذا مكان ما أنه يستمر للبرنامج بأكمله.إذن إما أن يكون هذا "تسريبًا" أو "ميزة حسب التصميم" اعتمادًا على كيفية استخدام "incr" (هل ستحتاج إلى استخدام هذه الوظيفة لبقية البرنامج بالكامل؟).على أية حال، النقطة الأساسية هنا هي أن 'incr' يحتفظ بموارد الذاكرة، لذلك إذا لم تكن بحاجة إلى هذه الموارد إلى الأبد، فيجب عليك الترتيب بحيث يصبح الإغلاق المشار إليه بواسطة 'incr' غير قابل للوصول عندما لا تكون هناك حاجة إليه.عادة قد يكون هذا عن طريق جعلها محلية لبعض الوظائف الأخرى، على سبيل المثال.

let MyComplicatedFuncThatNeedsALocalCounter args =
    let incr = 
        // as before
    // other code that uses incr
    // return some result that does not capture incr

نصائح أخرى

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

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

يتم تحرير العداد من الكومة عندما لا يكون من الممكن الوصول إلى incr.إنه ليس تسربًا للذاكرة بسبب جمع البيانات المهملة.

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