موقع Stack Overflow

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

الأسباب

التكرار اللانهائي

السبب الأكثر شيوعًا لتجاوز سعة المكدس هو الاستدعاء الذاتي العميق أو اللانهائي، حيث تستدعي الدالة نفسها مرات عديدة لدرجة أن المساحة اللازمة لتخزين المتغيرات والمعلومات المرتبطة بكل استدعاء تتجاوز المساحة التي يمكن أن تتسع لها المكدس. [ 2 ]

إليك مثال على الاستدعاء الذاتي اللانهائي في لغة C :

int foo () { return foo (); }

عند استدعاء الدالة foo ، تستمر في استدعاء نفسها، مُخصصةً مساحة إضافية في المكدس في كل مرة، حتى يفيض المكدس، مما يؤدي إلى خطأ تجزئة الذاكرة . [ 2 ] مع ذلك، تُطبّق بعض المُترجمات تحسين استدعاء الذيل ، مما يسمح بحدوث استدعاءات لا نهائية من نوع مُحدد - استدعاء الذيل - دون حدوث فيضان في المكدس. ينجح هذا لأن استدعاءات استدعاء الذيل لا تشغل مساحة إضافية في المكدس. [ 3 ]

تُتيح بعض خيارات مُصرّف لغة C تحسين استدعاءات الذيل ؛ فعلى سبيل المثال، سيؤدي تجميع البرنامج البسيط المذكور أعلاه باستخدام gcc مع خيار `--` إلى خطأ تجزئة الذاكرة، ولكن ليس عند استخدام خياري `--` أو ` --`، لأن مستويات التحسين هذه تستلزم خيار المُصرّف `--`. [ 4 ] تتطلب لغات أخرى، مثل Scheme ، أن تتضمن جميع تطبيقاتها الاستدعاءات التكرارية الذيلية كجزء من معيار اللغة. [ 5 ]-O1-O2-O3-foptimize-sibling-calls

تكرار عميق للغاية

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

class SomeObject { public : SomeObject * next ; };void someFunction ( SomeObject * argument ) { if ( getCondition ()) { someFunction ( argument . next ); } }
Stack < SomeObject *> s ;s.push ( argument ) ; while ( ! s.empty ( ) ) { argument = s.pop ( ) ; if ( condition ) { s.push ( argument.next ) ; } }

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

لن تُشكّل دالةٌ مثل المثال المذكور أعلاه على اليسار مشكلةً في بيئةٍ تدعم تحسين استدعاءات الدوال ؛ ومع ذلك، لا يزال من الممكن إنشاء دالةٍ تكراريةٍ قد تُؤدّي إلى تجاوز سعة المكدس في هذه اللغات. انظر المثال أدناه لدالتين بسيطتين لحساب الأسس الصحيحة.

دالة pow ( عدد صحيح base ، عدد صحيح exp ) { إذا كان ( exp > 0 ) { إرجاع base * pow ( base ، exp - 1 } وإلا { إرجاع 1 ؛ } }
int pow ( قاعدة int , int exp ) { return pow_accum ( base , exp , 1 ); }int pow_accum ( قاعدة int ، int exp ، int accum ) { if ( exp > 0 ) { return pow_accum ( base ، exp - 1 ، accum * base } آخر { عودة تراكم ؛ } }

تُنتج كلتا pow(base, exp)الدالتين أعلاه نتيجةً مكافئة، إلا أن الدالة الموجودة على اليسار عُرضةٌ للتسبب في تجاوز سعة المكدس لأن تحسين استدعاءات الدوال غير ممكن لهذه الدالة. أثناء التنفيذ، سيبدو مكدس هذه الدوال كما يلي:

pow ( 5 , 4 ) 5 * pow ( 5 , 3 ) 5 * ( 5 * pow ( 5 , 2 )) 5 * ( 5 * ( 5 * pow ( 5 , 1 ))) 5 * ( 5 * ( 5 * ( 5 * pow ( 5 , 0 )))) 5 * ( 5 * ( 5 * ( 5 * 1 ))) 625
أسرى ( 5 , 4 ) أسرى أسرى ( 5 , 4 , 1 ) أسرى أسرى ( 5 , 3 , 5 ) أسرى أسرى ( 5 , 2 , 25 ) أسرى أسرى ( 5 , 1 , 125 ) أسرى أسرى ( 5 , 0 , 625 ) 625

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

متغيرات مكدس كبيرة جدًا

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

مثال على متغير مكدس كبير جدًا في لغة C :

void foo () { double x [ 1048576 ] = { 0 }; }

في تطبيق C مع 8 بايت من الأعداد العشرية ذات الدقة المزدوجة ، تستهلك المصفوفة المعلنة 8 ميجابايت من البيانات؛ إذا كانت هذه الذاكرة أكبر من الذاكرة المتاحة على المكدس (كما هو محدد بواسطة معلمات إنشاء مؤشر الترابط أو حدود نظام التشغيل)، فسيحدث تجاوز سعة المكدس.

بيئة مقيدة

تتفاقم مشكلة تجاوز سعة المكدس عند أي شيء يقلل من حجم المكدس الفعلي لبرنامج معين. على سبيل المثال، قد يعمل البرنامج نفسه بشكل سليم عند تشغيله بدون خيوط متعددة، ولكن بمجرد تفعيل خاصية تعدد الخيوط، سيتعطل البرنامج. والسبب في ذلك هو أن معظم البرامج التي تدعم الخيوط لديها مساحة مكدس أقل لكل خيط مقارنةً ببرنامج لا يدعم الخيوط. ولأن أنظمة التشغيل عادةً ما تكون متعددة الخيوط، يُنصح المبتدئون في تطوير أنظمة التشغيل بتجنب استخدام الخوارزميات التكرارية أو مخازن المكدس الكبيرة. [ 7 ]

انظر أيضاً

مراجع

  1. بيرلي، جيمس كريج (1991-06-01). "استخدام ونقل لغة جنو فورتران" . مؤرشف من الأصل في 2012-02-06.
  2. ١ ٢ ما الفرق بين خطأ تجزئة الذاكرة وتجاوز سعة المكدس؟ مؤرشف بتاريخ ١٣ سبتمبر ٢٠٢١ في أرشيف الإنترنت على موقع Stack Overflow
  3. "مقدمة إلى لغة Scheme وتطبيقها" . 1997-02-19. مؤرشف من الأصل في 2007-08-10.
  4. "استخدام مجموعة مترجمات جنو (GCC): خيارات التحسين" . مؤرشف من الأصل بتاريخ 20 أغسطس 2017. تم الاطلاع عليه بتاريخ 20 أغسطس 2017 .
  5. ريتشارد كيلسي؛ ويليام كلينجر؛ جوناثان ريس؛ وآخرون . (أغسطس 1998). "التقرير الخامس المنقح حول لغة Scheme الخوارزمية" . الحوسبة الرمزية والحسابية من الرتبة العليا . 11 (1): 7-105 . doi : 10.1023/A:1010051815785 . S2CID 14069423. مؤرشف من الأصل في 5 يناير 2007. تم الاطلاع عليه في 9 أغسطس 2012 .  
  6. فيلدمان، هوارد (23 نوفمبر 2005). "إدارة الذاكرة الحديثة، الجزء الثاني" . مؤرشف من الأصل في 20 سبتمبر 2012. تم الاطلاع عليه في 14 أغسطس 2007 .
  7. "دليل برمجة النواة: نصائح حول الأداء والاستقرار" . شركة آبل . 2014-05-02. مؤرشف من الأصل في 2014-05-03 . تم الاطلاع عليه في 2014-05-02 .