متى يتم تحرير قيمة الحالة القابلة للتغيير من الكومة؟
-
03-07-2019 - |
سؤال
على 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.إنه ليس تسربًا للذاكرة بسبب جمع البيانات المهملة.