Setjmp.h

<setjmp.h> هو ملف رأس مُعرَّف في مكتبة C القياسية لتوفير "قفزات غير محلية": أي التحكم في مسار التنفيذ الذي يختلف عن تسلسل استدعاء وإرجاع الروتينات الفرعية المعتادsetjmpوتُوفِّرlongjmpهذه الوظيفة.

يُستخدم الرمز setjmp/ عادةً longjmpلتنفيذ آلية استثناءات تستغل قدرة longjmpإعادة تهيئة حالة البرنامج أو الخيط، حتى عبر مستويات متعددة من استدعاءات الدوال. أما استخدامه الأقل شيوعًا setjmpفهو لإنشاء صيغة مشابهة للروتينات الفرعية .

الدوال الأعضاء

intsetjmp(jmp_bufenv)
يقوم هذا الإجراء بتهيئة jmp_bufالمخزن المؤقت المحلي وتهيئته للقفز. [ 1 ] يحفظ بيئة البرنامج المستدعية في مخزن البيئة المحدد بواسطة envالوسيط لاستخدامها لاحقًا longjmp. إذا كانت القيمة المرجعة من استدعاء مباشر، setjmpفإنه يُرجع 0. أما إذا كانت القيمة المرجعة من استدعاء لـ longjmp، setjmpفإنه يُرجع قيمة غير صفرية.
voidlongjmp(jmp_bufenv,intvalue)
يستعيد هذا الإجراء سياق مخزن البيئة envالذي تم حفظه باستدعاء الروتين setjmp[ 1 ] في نفس استدعاء البرنامج. الاستدعاء longjmpمن معالج إشارة متداخل غير مُعرَّفvalue . يتم تمرير القيمة المُحدَّدة بواسطة من longjmpإلى setjmp. بعد longjmpاكتمال ، يستمر تنفيذ البرنامج كما لو أن الاستدعاء المقابل لـ setjmpقد انتهى للتو. إذا كانت القيمة valueالمُمرَّرة إلى longjmpهي 0، setjmpفسيتصرف كما لو أنه أعاد 1؛ وإلا، فسيتصرف كما لو أنه أعاد value.

setjmpيحفظ هذا الإجراء بيئة البرنامج الحالية (حالة البرنامج) في مرحلة معينة من تنفيذه، ضمن بنية بيانات خاصة بالمنصة jmp_buf، والتي يمكن استخدامها لاحقًا longjmpلاستعادة حالة البرنامج إلى الحالة المحفوظة setjmp. jmp_bufيمكن تصور هذه العملية على أنها "قفزة" إلى نقطة تنفيذ البرنامج التي تم عندها setjmpحفظ البيئة. تشير القيمة المُعادة (الظاهرية) من هذا الإجراء setjmpإلى ما إذا كان التحكم قد وصل إلى تلك النقطة بشكل طبيعي (صفر) أو من خلال استدعاء (قيمة غير صفرية) . longjmpوهذا ما يُفسر شيوع هذا التعبير :if(setjmp(x)){/* handle longjmp(x) */}

لا يحدد معيار POSIXsetjmp .1 ما إذا كان يجب حفظ longjmpواستعادة مجموعة الإشارات المحظورة الحالية ؛ إذا كان البرنامج يستخدم معالجة الإشارات، فيجب عليه استخدام معيار POSIX sigsetjmp/ siglongjmp.

أنواع الأعضاء

jmp_buf
نوع مصفوفة مناسب لحفظ المعلومات اللازمة لاستعادة بيئة الاستدعاء.

يصف معيار C99 jmp_bufنوع المصفوفة بأنه نوعٌ مُصمّمٌ للتوافق مع الإصدارات السابقة ؛ إذ يشير الكود الحالي إلى jmp_bufمواقع التخزين بالاسم (بدون &عامل عنوان)، وهو أمرٌ ممكنٌ فقط لأنواع المصفوفات. [ 2 ] ويشير المعيار إلى أنه يمكن أن يكون ببساطة مصفوفةً ذات عنصرٍ واحدٍ فقط، حيث يُمثّل هذا العنصر البيانات الفعلية؛ في الواقع، هذا هو النهج الذي تتبعه مكتبة GNU C ، والتي تُعرّف النوع على أنه struct __jmp_buf_tag[1].

التحذيرات والقيود

عند تنفيذ أمر "goto" غير محلي باستخدام setjmp/ longjmpفي لغة C++ ، لا يتم تنفيذ عملية " فكّ التراكم " المعتادة . وبالتالي، لن تُنفَّذ أي إجراءات تنظيف مطلوبة. قد يشمل ذلك إغلاق مُعرِّفات الملفات ، أو تفريغ المخازن المؤقتة ، أو تحرير الذاكرة المُخصَّصة في الكومة .

إذا انتهت الدالة التي setjmpتم استدعاؤها، فلن يكون من الممكن استخدامها بأمان longjmpمع الكائن المقابل jmp_buf. وذلك لأن إطار المكدس يصبح غير صالح عند انتهاء الدالة. يؤدي استدعاء الدالة longjmpإلى استعادة مؤشر المكدس ، والذي - نظرًا لأن الدالة انتهت - سيشير إلى إطار مكدس غير موجود، وربما يكون قد تم استبداله أو تلفه. [ 3 ] [ 4 ]

وبالمثل، لا يشترط معيار C99longjmp الحفاظ على إطار المكدس الحالي. وهذا يعني أن الانتقال إلى دالة تم الخروج منها عبر استدعاء longjmpغير مُعرَّف. [ 5 ]

مثال على الاستخدام

مثال بسيط

يوضح المثال أدناه الفكرة الأساسية لـ setjmp. هناك، main()يتم استدعاء first()، والتي بدورها تستدعي second(). ثم، second()يقفز عائدًا إلى main()، متجاوزًا first()استدعاء لـ printf().

#include <setjmp.h> #include <stdio.h>static jmp_buf buf ;void second () { printf ( "second \n " ); // يطبع longjmp ( buf , 1 ); // يقفز للخلف إلى حيث تم استدعاء setjmp - مما يجعل setjmp الآن يُرجع 1 }void first () { second (); printf ( "first \n " ); // لا يتم الطباعة }int main () { if ( ! setjmp ( buf )) { first (); // عند التنفيذ، تُرجع setjmp القيمة 0 } else { // عند قفز longjmp للخلف، تُرجع setjmp القيمة 1 printf ( "main \n " ); // يطبع }return 0 ; }

عند تنفيذ البرنامج أعلاه، سيُخرج المخرجات التالية:

الشارع الرئيسي الثاني

لاحظ أنه على الرغم من first()استدعاء الروتين الفرعي، إلا firstأنه لا يتم طباعة " " أبدًا، لأن second()الدالة لا تعيد التحكم إلى first(). بدلاً من ذلك، mainيتم طباعة " " عند التحقق من العبارة الشرطية if (!setjmp(buf))مرة ثانية.

معالجة الاستثناءات

في هذا المثال، setjmpيُستخدم `_`_`_`_`_`_`_`_``_```_``````````````````````````````````````````````````````````````````````````````````````"`````"`"```"`"`"`"`"`"`"`"""""""" " " try" لذا لذا " ` ` ` ` " ...longjmpthrowsetjmpsetjmp

  • كشرط ifلعبارة switchتكرارية
  • كما سبق بالاقتران مع واحد !أو مقارنة مع ثابت صحيح
  • كعبارة (مع عدم استخدام قيمة الإرجاع)

اتباع هذه القواعد يُسهّل على التطبيق إنشاء مخزن بيانات البيئة، وهي عملية حساسة. [ 2 ] قد يؤدي الاستخدام العام إلى setjmpسلوك غير مُحدد، مثل تلف المتغيرات المحلية؛ ولا يُشترط على المُترجمات والبيئات المُتوافقة حماية أو حتى تحذير من هذا الاستخدام. مع ذلك، فإنّ أساليب أكثر تعقيدًا، مثل [2]، شائعة في الأدبيات والتطبيق، وتظل قابلة للنقل نسبيًا. تُعرض أدناه منهجية مُتوافقة بسيطة، حيث يتم الاحتفاظ بمتغير إضافي مع مخزن بيانات الحالة. يُمكن تطوير هذا المتغير إلى بنية تتضمن المخزن نفسه.switch((exception_type=setjmp(env))){}

في مثال ذي مظهر أكثر حداثة، سيتم تنفيذ كتلة "try" المعتادة كـ setjmp (مع بعض التعليمات البرمجية التحضيرية للقفزات متعددة المستويات، كما هو موضح في first)، و"throw" كـ longjmp مع المعلمة الاختيارية كاستثناء، و"catch" ككتلة "else" ضمن "try".

#include <setjmp.h> #include <stdio.h> #include <stdlib.h> #include <string.h>دالة ثابتة void first (); دالة ثابتة void second ();/* استخدم متغيرًا ثابتًا ضمن نطاق الملف لمكدس الاستثناءات حتى نتمكن من الوصول إليه في أي مكان داخل وحدة الترجمة هذه. */ static jmp_buf exception_env ; static int exception_type ;int main ( void ) { volatile char * mem_buffer = NULL ;إذا ( تم استدعاء setjmp ( exception_env )) { // إذا وصلنا إلى هنا، فهذا يعني وجود استثناء printf ( "فشلت الدالة first، نوع الاستثناء: %d \n " , exception_type ); } else { // تشغيل الكود الذي قد يشير إلى الفشل عبر longjmp. puts ( "استدعاء الدالة first" ); first ();mem_buffer = ( char * ) malloc ( 300 ); // تخصيص مورد printf ( "%s \n " , strcpy ( mem_buffer , "first succeeded" )); // لم يتم الوصول إليه }free ( mem_buffer ); // يمكن تمرير قيمة NULL إلى دالة free، ولن يتم تنفيذ أي عملية.return 0 ; }static void first () { jmp_buf my_env ;puts ( "الدخول أولاً" ); // تم الوصولmemcpy ( my_env , exception_env , sizeof my_env ); // تخزين قيمة exception_env في my_env لأن exception_env سيتم إعادة استخدامهاswitch ( setjmp ( exception_env )) { case 3 : // إذا وصلنا إلى هنا، فهذا يعني وجود استثناء. printf ( "فشلت العملية الثانية، نوع الاستثناء: 3؛ إعادة تعيينه إلى النوع 1" ); exception_type = 1 ;الوضع الافتراضي : // المتابعة memcpy ( exception_env , my_env , sizeof exception_env ); // استعادة مكدس الاستثناء longjmp ( exception_env , exception_type ); // متابعة معالجة الاستثناءالحالة 0 : // عملية طبيعية ومرغوبة printf ( "استدعاء الدالة الثانية" ); // تم الوصول إلى الدالة الثانية (); printf ( "تم استدعاء الدالة الثانية بنجاح" ); // لم يتم الوصول إليها }memcpy ( exception_env , my_env , sizeof exception_env ); // استعادة مكدس الاستثناءاتputs ( "المغادرة أولاً" ); // لم يتم الوصول إليها مطلقاً }static void second () { printf ( "بدء الثانية" ); // تم الوصولexception_type = 3 ; longjmp ( exception_env , exception_type ); // إعلان فشل البرنامجprintf ( "المغادرة الثانية" ); // لم يتم الوصول إليها }

مخرجات هذا البرنامج هي:

استدعاء الأول ، دخول الأول، استدعاء الثاني ، دخول الثاني، فشل الثاني، نوع الاستثناء: 3؛ إعادة التعيين إلى النوع 1، فشل الأول، نوع الاستثناء: 1

تعدد المهام التعاوني

تنصّ C99 على longjmpضمان عمل هذه الخاصية فقط عندما تكون الوجهة دالة مُستدعِية، أي أن نطاق الوجهة مضمونٌ أن يبقى سليمًا. الانتقال إلى دالة انتهت بالفعل بواسطة `get` returnأو ` longjmpget` غير مُعرَّف. [ 5 ] مع ذلك، لا تُدمِّر معظم تطبيقات `get` longjmpالمتغيرات المحلية تحديدًا عند إجراء الانتقال. بما أن السياق يبقى حتى تُمسح متغيراته المحلية، فإنه يُمكن استعادته بواسطة `get` setjmp. في العديد من البيئات (مثل Really Simple Threads و TinyTimbers )، تسمح اصطلاحات مثل `get` للدالة المُستدعاة بالتوقف مؤقتًا ثم استئناف التنفيذ عند `get` .if(!setjmp(child_env))longjmp(caller_env);setjmp

يتم استغلال هذا بواسطة مكتبات الخيوط لتوفير مرافق تعدد المهام التعاونية دون استخدام مرافق الأليافsetcontext الأخرى .

بالنظر إلى أن setjmpوظيفة الطفل ستعمل بشكل عام ما لم يتم تخريبها، و setcontextكجزء من POSIX، لا يلزم توفيرها بواسطة تطبيقات C، فقد تكون هذه الآلية قابلة للنقل حيث setcontextيفشل البديل.

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

#include <setjmp.h> #include <stdio.h>jmp_buf mainTask , childTask ;void call_with_cushion (); void child ();int main () { if ( ! setjmp ( mainTask )) { call_with_cushion (); // لا يعود البرنامج الفرعي أبدًا، يتم التنازل } // يستأنف التنفيذ بعد هذا "}" بعد أول مرة يتنازل فيها البرنامج الفرعيبينما ( صحيح ) { printf ( "الأصل \n " ); إذا ( ! setjmp ( mainTask )) { longjmp ( childTask , 1 ); // yield - لاحظ أن هذا غير مُعرَّف في C99 } } }void call_with_cushion () { char space [ 1000 ]; // حجز مساحة كافية لتشغيل الدالة الرئيسية space [ 999 ] = 1 ; // عدم تحسين المصفوفة خارج نطاقها child (); }void child () { while ( true ) { printf ( "بداية حلقة الطفل \n " ); if ( ! setjmp ( childTask )) { longjmp ( mainTask , 1 ); // yield - يُبطل childTask في C99 }printf ( "نهاية حلقة الطفل \n " );إذا لم يتم تعيين قفزة ( setjmp ( childTask )) { قفزة طويلة (longjmp ( mainTask , 1 ); // yield - يُبطل صلاحية childTask في C99 } }/* لا تقم بالعودة. بدلاً من ذلك، يجب علينا تعيين علامة للإشارة إلى أن الدالة main()  يجب أن تتوقف عن التنازل لنا، ثم استخدام longjmp(mainTask, 1) */ }

مراجع

  1. 1 2 تنص ISO C على أنهsetjmpيجب تنفيذها كـ macro، لكن POSIX تنص صراحة على أنه من غير المحدد ما إذاsetjmpكانت عبارة عن macro أو دالة.
  2. 1 2 C99 الأساس المنطقي، الإصدار 5.10، أبريل 2003 ، القسم 7.13
  3. ملاحظات محاضرة CS360 — Setjmp و Longjmp
  4. setjmp(3) مؤرشف بتاريخ 26-07-2009 في أرشيف الإنترنت
  5. 1 2 ISO/IEC 9899:1999 ، 2005، 7.13.2.1:2 والحاشية 211
  6. : تعيين نقطة قفز لأمر goto غير محلي مرجع واجهات النظام، مواصفات يونكس الموحدة ، الإصدار 5 من مجموعة Open Groupsetjmp   

للمزيد من القراءة