إدارة الذاكرة اليدوية
في علوم الحاسوب ، تشير إدارة الذاكرة اليدوية إلى استخدام المبرمج لتعليمات يدوية لتحديد وتحرير الكائنات غير المستخدمة ، أو ما يُعرف بالبيانات المهملة . وحتى منتصف التسعينيات، كانت غالبية لغات البرمجة المستخدمة في الصناعة تدعم إدارة الذاكرة اليدوية، على الرغم من أن خاصية جمع البيانات المهملة موجودة منذ عام ١٩٥٩، عندما تم تقديمها مع لغة ليسب . [ ١ ] أما اليوم، فتزداد شعبية اللغات التي تدعم جمع البيانات المهملة، مثل جافا، وتوفر لغتا أوبجكتيف-سي وسويفت وظائف مماثلة من خلال عد المراجع التلقائي . أما اللغتان الرئيسيتان اللتان لا تزالان تستخدمان إدارة الذاكرة اليدوية على نطاق واسع اليوم فهما سي وسي ++ - انظر تخصيص الذاكرة الديناميكي في لغة سي .
وصف
تستخدم العديد من لغات البرمجة أساليب يدوية لتحديد وقت تخصيص كائن جديد من الذاكرة الحرة. تستخدم لغة C mallocالدالة `resource`، بينما تستخدم لغتا C++ وجافا newالمعامل `resource`، وتُخصص العديد من اللغات الأخرى (مثل بايثون) جميع الكائنات من الذاكرة الحرة. يُعد تحديد وقت إنشاء كائن ما ( إنشاء الكائن ) عملية بسيطة وغير معقدة عمومًا، على الرغم من أن تقنيات مثل مجمعات الكائنات تسمح بإنشاء الكائن قبل استخدامه مباشرةً. يكمن التحدي الحقيقي في تدمير الكائن - أي تحديد متى يصبح الكائن غير ضروري (أي أنه أصبح بيانات مهملة)، وترتيب إعادة تخزينه إلى الذاكرة الحرة لإعادة استخدامه. في تخصيص الذاكرة اليدوي، يُحدد المبرمج ذلك يدويًا أيضًا؛ عبر دوال مثل `resource` free()في لغة C، أو deleteالمعامل `resource` في لغة C++ - وهذا يختلف عن التدمير التلقائي للكائنات المخزنة في متغيرات تلقائية ، لا سيما المتغيرات المحلية (غير الثابتة) للدوال، والتي تُدمر عند انتهاء نطاقها في لغتي C وC++.
تقنيات إدارة الذاكرة اليدوية
يستخدم المبرمجون مجموعة متنوعة من الأساليب اليدوية للتحكم في دورة حياة الكائنات وإعادة استخدام الذاكرة. تشمل الأنماط الشائعة ما يلي:
- التخصيص والإلغاء الصريح للذاكرة - يقوم المبرمج باستدعاء وظائف مثل
malloc()وfree()في لغة C، أو عوامل التشغيلnewوdeleteفي لغة C++، للحصول على الذاكرة من وإعادتها إلى الكومة . - ساحات الذاكرة (الذاكرة القائمة على المناطق) - يتم تخصيص العديد من الكائنات من كتلة متجاورة واحدة يتم إلغاء تخصيصها دفعة واحدة، مما يبسط إدارة دورة الحياة ويحسن الموضعية.
- مخازن مؤقتة مؤقتة - تخصيصات مؤقتة قصيرة الأجل يتم إعادة تدويرها بين التكرارات أو الإطارات، وغالبًا ما تستخدم في تطوير الألعاب والأنظمة المدمجة لتجنب التفاعل المتكرر مع الكومة.
- مجموعات الكائنات - مجموعات مخصصة مسبقًا من الكائنات القابلة لإعادة الاستخدام والتي يتم توزيعها وإعادتها، مما يلغي الحاجة إلى تخصيص/إلغاء تخصيص كل كائن.
- المخصصات المخصصة - استراتيجيات تخصيص متخصصة (مثل مخصصات المكدس، ومخصصات القائمة الحرة، ومخصصات الشرائح) مصممة خصيصًا لأنماط استخدام معينة لتحسين الأداء أو تقليل التجزئة.
- الاستحواذ على الموارد هو التهيئة (RAII) - ربط عمر المورد (بما في ذلك الذاكرة) بنطاق الكائن عن طريق الحصول عليه في المُنشئ وتحريره في المُدمر؛ يستخدم على نطاق واسع في C++ و Rust (انظر § الاستحواذ على الموارد هو التهيئة ).
- Placement new (C++) – إنشاء كائن في ذاكرة مخصصة مسبقًا، وفصل تخصيص الذاكرة عن إنشاء الكائن.
- Defer – بنية لغوية تقوم بجدولة تنفيذ التعليمات البرمجية للتنظيف عند الخروج من نطاق أو دالة؛ موجودة في Go و Zig و C29 (انظر § Defer ).
غالباً ما تمنح التقنيات اليدوية المبرمج تحكماً دقيقاً في الأداء واستخدام الموارد، ولكنها أيضاً تُدخل خطر حدوث أخطاء مثل المؤشرات المعلقة ، وتسرب الذاكرة ، والسلوك غير المحدد في حالة استخدامها بشكل غير صحيح.
الإدارة اليدوية والصحة
من المعروف أن إدارة الذاكرة اليدوية تسمح بدخول العديد من أنواع الأخطاء الرئيسية إلى البرنامج عند استخدامها بشكل غير صحيح، ولا سيما انتهاكات سلامة الذاكرة أو تسريبات الذاكرة . وتُعد هذه الأخطاء مصدراً مهماً للثغرات الأمنية . [ 2 ]
- عندما لا يتم تحرير كائن غير مستخدم إلى الذاكرة الحرة، يُعرف ذلك بتسرب الذاكرة . في بعض الحالات، قد يكون تسرب الذاكرة مقبولاً، مثل برنامج "يُسرّب" كمية محدودة من الذاكرة خلال فترة تشغيله، أو برنامج قصير يعتمد على نظام التشغيل لتحرير موارده عند انتهاء تشغيله. مع ذلك، في كثير من الأحيان، يحدث تسرب الذاكرة في البرامج طويلة الأمد، وفي هذه الحالة تُسرّب كمية غير محدودة من الذاكرة. عند حدوث ذلك، يستمر حجم الذاكرة الحرة المتاحة في التناقص بمرور الوقت؛ وعندما تُستنفد هذه الذاكرة، يتعطل البرنامج.
- قد ينتج عن حذف ذاكرة الدعم لكائن ما أكثر من مرة فشل كارثي في نظام إدارة الذاكرة الديناميكية ؛ أو عند تدمير كائن ما بشكل صريح أكثر من مرة؛ أو عند محاولة المبرمج، أثناء استخدام مؤشر لمعالجة كائن غير مخصص في الذاكرة الحرة، تحرير ذاكرة الدعم للكائن المستهدف؛ أو عند قيام المبرمج، أثناء معالجة كائن ما عبر مؤشر إلى منطقة ذاكرة أخرى عشوائية تُدار بواسطة مهمة أو خيط أو عملية خارجية غير معروفة، بإتلاف حالة ذلك الكائن، ربما بطريقة تؤدي إلى الكتابة خارج حدوده وإتلاف بيانات إدارة الذاكرة الخاصة به. وتشمل نتائج هذه الإجراءات تلف الكومة ، والتدمير المبكر لكائن آخر (تم إنشاؤه حديثًا) يشغل نفس موقع الذاكرة الذي يشغله الكائن المحذوف عدة مرات، وتعطل البرنامج بسبب خطأ تجزئة (انتهاك حماية الذاكرة )، وأشكال أخرى من السلوك غير المحدد .
- تصبح المؤشرات إلى الكائنات المحذوفة مؤشرات عشوائية إذا تم استخدامها بعد الحذف؛ ومحاولة استخدام هذه المؤشرات يمكن أن تؤدي إلى أخطاء يصعب تشخيصها.
من المعروف أن اللغات التي تستخدم جمع البيانات المهملة بشكل حصري تتجنب النوعين الأخيرين من العيوب. قد تحدث تسريبات الذاكرة (وتحدث تسريبات محدودة بشكل متكرر مع جمع البيانات المهملة التدريجي أو المحافظ)، ولكنها عمومًا أقل حدة من تسريبات الذاكرة في الأنظمة اليدوية.
يُعدّ الحصول على الموارد عملية تهيئة.
تتمتع إدارة الذاكرة اليدوية بميزة واحدة تتعلق بالصحة، وهي أنها تسمح بإدارة الموارد تلقائيًا من خلال نموذج اكتساب الموارد وتهيئة الذاكرة (RAII).
ينشأ هذا عندما تمتلك الكائنات موارد نظام نادرة (مثل موارد الرسومات، أو مؤشرات الملفات، أو اتصالات قواعد البيانات) والتي يجب التخلي عنها عند تدمير الكائن - حيث ينبغي ربط مدة ملكية الموارد بمدة حياة الكائن. يمكن للغات التي تعتمد على الإدارة اليدوية تنظيم ذلك من خلال الحصول على المورد أثناء تهيئة الكائن (في الدالة البانية)، وتحريره أثناء تدمير الكائن (في الدالة المدمرة ) ، والذي يحدث في وقت محدد. يُعرف هذا باسم "الحصول على الموارد أثناء التهيئة" (RAII).
يمكن استخدام هذا أيضًا مع عدّ المراجع الحتمي . في لغة C++، تُستغل هذه الإمكانية بشكل أكبر لأتمتة تحرير الذاكرة ضمن إطار عمل يدوي، حيث يُعد استخدام المؤشرات الذكية في المكتبة القياسية للغة لإدارة الذاكرة نموذجًا شائعًا. في لغات مثل Rust التي تفرض ملكية صارمة، يصبح RAII السلوك الافتراضي لإدارة الموارد. [ 3 ]
لا يُمكن استخدام هذا الأسلوب في معظم لغات البرمجة التي تعتمد على جمع البيانات المهملة - لا سيما في جامعات البيانات المهملة التي تعتمد على التتبع أو في عدّ المراجع الأكثر تقدماً - لأن عملية الإنهاء غير حتمية، وأحياناً لا تحدث على الإطلاق. أي أنه من الصعب تحديد متى أو ما إذا كان سيتم استدعاء دالة الإنهاء ؛ وهذا ما يُعرف بمشكلة الإنهاء . غالباً ما تستخدم لغة جافا وغيرها من اللغات التي تُطبّق جامع البيانات المهملة إدارة يدوية لموارد النظام المحدودة، باستثناء الذاكرة، عبر نمط التخلص (dispose) : يُتوقع من أي كائن يُدير الموارد أن يُطبّق الدالة dispose()التي تُحرّر هذه الموارد وتُعلّم الكائن بأنه غير نشط. يُتوقع من المبرمجين استدعاء dispose()هذه الدالة يدوياً حسب الحاجة لمنع "تسريب" موارد الرسومات المحدودة. بالنسبة لموارد المكدس (الموارد التي يتم الحصول عليها وتحريرها ضمن كتلة واحدة من التعليمات البرمجية)، يُمكن أتمتة ذلك باستخدام بنيات لغوية مختلفة، مثل Python و withC# و usingwith-resources (التي تستدعي دالة الكائن Dispose()) أو Java try(التي يُمكن استخدامها على أي كائن يُطبّق دالة java.lang.AutoCloseableويستدعيها close()).
تأجيل
آلية التأجيل هي ميزة تسمح بتأجيل تنفيذ جزء من التعليمات البرمجية إلى وقت لاحق، عادةً إما عند نهاية نطاق التعليمات البرمجية أو نهاية دالة. ويمكن تشبيه ذلك بكتلة finally في لغات البرمجة الأخرى.
ج
تُقدّم لغة C (ابتداءً من C29 ) deferآليةً جديدة. يتم تشغيل كل deferكتلة يتم مصادفتها أثناء التنفيذ عند الخروج من النطاق (وليس عند مصادفتها)، بترتيب عكسي لترتيب مصادفتها. [ 4 ]
#include <stddefer.h> #include <stdio.h> #include <stdlib.h>دالة معالجة الملف ( مسار الملف ) { ملف * f = فتح الملف ( المسار ، "r" ) ; إذا ( ! f ) { إرجاع -1 ؛ }defer { printf ( "إغلاق الملف. \n " ); fclose ( f ); };char * buffer = malloc ( 1024 ); if ( ! buffer ) { return -2 ; }defer { printf ( "تحرير المخزن المؤقت. \n " ); free ( buffer ); };// محاكاة عمليات الخروج المبكر المتعددة إذا ( fgets ( buffer , 1024 , f )) { return -3 ; // لا يزال كلا التأجيلين قيد التشغيل }إذا كان ( buffer [ 0 ] == '#' ) { return -4 ; // كلا التأجيلين لا يزالان قيد التشغيل }printf ( "جاري المعالجة: %s \n " , buffer ); return 0 ; }لم تُعلّق لغة C++ بعد على ما إذا كانت ستدمج لغة C أم لا defer، حيث أن RAII يغطي حالة الاستخدام هذه.
يذهب
في لغة Go ، deferتُستخدم هذه الدالة لجدولة استدعاء دالة ليتم تشغيلها عند انتهاء الدالة المحيطة بها. deferيتم تشغيل عدة استدعاءات بترتيب "آخر ما يدخل أول ما يخرج" (LIFO).
الحزمة الرئيسيةاستيراد "fmt"func main ( ) { fmt.Println ( " start" )defer fmt.Println ( " first defer" ) defer fmt.Println ( " second defer " )fmt.Println ( " end " ) }زيج
في لغة Zig ، deferيكون التنفيذ على مستوى الكتلة ويتم تشغيله عند انتهاء النطاق الحالي، وليس فقط عند انتهاء الدالة. على عكس لغة Go، deferلا تتكبد Zig أي تكاليف إضافية.
const std = @import ( "std" );pub fn main () void { std . debug . print ( "start \n " , .{});{ defer std.debug.print ( "inner defer \ n " , .{}); std.debug.print ( " inside block \ n " , . { } ) ; }std.debug.print ( "end \n " , . { } ) ; }يتضمن Zig أيضًا errdefer، والذي يتم تشغيله فقط إذا خرجت الدالة بخطأ.
أداء
يُجادل العديد من مُؤيدي إدارة الذاكرة اليدوية بأنها تُوفر أداءً أفضل مُقارنةً بالتقنيات التلقائية مثل جمع البيانات المهملة . تقليديًا، كان زمن الاستجابة هو الميزة الأكبر، لكن هذا لم يعد صحيحًا. غالبًا ما يتميز التخصيص اليدوي بميزة أفضل في موضعية الوصول .
يُعرف التخصيص اليدوي بأنه أكثر ملاءمة للأنظمة التي تعاني من ندرة الذاكرة، نظرًا لسرعة استعادتها. وتشهد أنظمة الذاكرة، بشكل متكرر، حالة "التذبذب" عندما يقترب حجم مجموعة عمل البرنامج من حجم الذاكرة المتاحة؛ إذ تبقى الكائنات غير المستخدمة في نظام جمع البيانات المهملة في حالة غير مستعادة لفترة أطول من الأنظمة المُدارة يدويًا، لأنها لا تُستعاد فورًا، مما يزيد من حجم مجموعة العمل الفعلي.
تتضمن الإدارة اليدوية عدداً من عيوب الأداء الموثقة :
- تُضيف استدعاءات الدوال
deleteوما شابهها عبئًا إضافيًا في كل مرة تُجرى فيها، ويمكن توزيع هذا العبء على دورات جمع البيانات المهملة. وينطبق هذا بشكل خاص على التطبيقات متعددة الخيوط، حيث يجب مزامنة استدعاءات الحذف. - قد تكون عملية تخصيص الذاكرة أكثر تعقيدًا وأبطأ. بعض آليات جمع البيانات المهملة، مثل تلك التي تعتمد على ضغط الكومة ، يمكنها الحفاظ على مساحة التخزين الحرة كمصفوفة بسيطة من الذاكرة (على عكس التطبيقات المعقدة التي تتطلبها آليات الإدارة اليدوية).
زمن الاستجابة هو نقطة مثيرة للجدل تغيرت بمرور الوقت، حيث كانت جامعات القمامة المبكرة والتطبيقات البسيطة تعمل بشكل سيئ للغاية مقارنة بإدارة الذاكرة اليدوية، ولكن جامعات القمامة الحديثة المتطورة غالبًا ما تعمل بشكل جيد أو أفضل من إدارة الذاكرة اليدوية.
لا يعاني التخصيص اليدوي من فترات "التوقف" الطويلة التي تحدث في عملية جمع القمامة البسيطة التي توقف العالم، على الرغم من أن جامعات القمامة الحديثة لديها دورات جمع غالبًا ما تكون غير ملحوظة.
تعاني كل من إدارة الذاكرة اليدوية وجمع البيانات المهملة من احتمالية وجود أوقات تحرير غير محدودة - إدارة الذاكرة اليدوية لأن تحرير كائن واحد قد يتطلب تحرير أعضائه، وبشكل متكرر تحرير أعضاء أعضائه، وهكذا، بينما قد تستغرق عملية جمع البيانات المهملة دورات طويلة. وتُعد هذه مشكلة خاصة في الأنظمة الآنية ، حيث تُعتبر دورات التحرير غير المحدودة غير مقبولة عمومًا؛ إذ يُمكن إجراء جمع البيانات المهملة في الوقت الحقيقي عن طريق إيقاف جامع البيانات المهملة مؤقتًا، بينما تتطلب إدارة الذاكرة اليدوية في الوقت الحقيقي تجنب عمليات التحرير الكبيرة، أو إيقاف التحرير مؤقتًا يدويًا.
انظر أيضاً
مراجع
- ↑ مكارثي، جون؛ أبراهامز، بول دبليو؛ إدواردز، دانيال جيه؛ هارت، تيموثي بي؛ ليفين، مايكل آي. (1985) [1962]. دليل مبرمج لغة ليسب 1.5 (ملف PDF) . الطبعة الخامسة عشرة ( الطبعة الثانية). ص. مقدمة.
- ↑ "مبتكر لغة C++ يدعو إلى اتخاذ إجراءات للتصدي لـ'الهجمات الخطيرة'"تمت أرشفة هذا النص من المصدر الأصلي بتاريخ 14 يناير 2026. تم الاطلاع عليه بتاريخ 16 يناير 2026 .
- ↑ فريق Rust (19 أبريل 2026). "RAII - Rust من خلال الأمثلة" . doc.rust-lang.org . فريق Rust.
- ↑ جان هيد مينيد (2025). "البرمجة - سي - دالة defer، آلية للتراجع العام القائم على النطاق المعجمي" (ملف PDF) . open-std.org . WG 14.
- بيرغر، إي دي؛ زورن، بي جي؛ ماكينلي، كيه إس (نوفمبر 2002). "إعادة النظر في تخصيص الذاكرة المخصص" (ملف PDF) . وقائع المؤتمر السابع عشر لجمعية ACM SIGPLAN حول البرمجة الكائنية التوجه، والأنظمة، واللغات، والتطبيقات . OOPSLA '02. الصفحات 1-12 . CiteSeerX 10.1.1.119.5298 . doi : 10.1145/582419.582421 . ISBN 1-58113-471-1.
روابط خارجية
- مرجع إدارة الذاكرة ( مؤرشف بتاريخ 13 ديسمبر 2020 على موقع Wayback Machine)
- ريتشارد جونز ورافائيل لينز، جمع البيانات المهملة: خوارزميات لإدارة الذاكرة الديناميكية المؤتمتة ، وايلي وأولاده (1996)، رقم ISBN 0-471-94148-4
- إدارة الذاكرة
