Adakah yang bisa menjelaskan di mana tepatnya setjmp()
dan longjmp()
fungsi dapat digunakan secara praktis dalam pemrograman tertanam? Saya tahu bahwa ini untuk penanganan kesalahan. Tapi saya ingin tahu beberapa kasus penggunaan.
99
longjmp()
untuk keluar dari penangan sinyal, terutama hal-hal seperti aBUS ERROR
. Sinyal ini biasanya tidak dapat dimulai ulang. Aplikasi yang disematkan mungkin ingin menangani kasus ini demi keamanan dan pengoperasian yang kuat.setjmp
antara BSD dan Linux, lihat "Timing setjmp, dan Joy of Standards" , yang menyarankan penggunaansigsetjmp
.Jawaban:
Penanganan kesalahan
Misalkan ada kesalahan jauh di dalam fungsi yang bersarang di banyak fungsi lain dan penanganan kesalahan hanya masuk akal di fungsi tingkat atas.
Akan sangat membosankan dan canggung jika semua fungsi di antaranya harus kembali normal dan mengevaluasi nilai yang dikembalikan atau variabel kesalahan global untuk menentukan bahwa pemrosesan lebih lanjut tidak masuk akal atau bahkan akan buruk.
Itu adalah situasi di mana setjmp / longjmp masuk akal. Situasi tersebut mirip dengan situasi di mana pengecualian dalam bahasa lain (C ++, Java) masuk akal.
Coroutines
Selain penanganan error, saya juga bisa memikirkan situasi lain di mana Anda memerlukan setjmp / longjmp di C:
Ini adalah kasus ketika Anda perlu mengimplementasikan coroutine .
Berikut adalah contoh demo kecil. Saya berharap ini memenuhi permintaan dari Sivaprasad Palas untuk beberapa kode contoh dan menjawab pertanyaan TheBlastOne bagaimana setjmp / longjmp mendukung implementasi corroutines (sejauh yang saya lihat itu tidak berdasarkan pada perilaku non-standar atau baru).
EDIT:
Bisa jadi itu benar-benar adalah perilaku undefined melakukan
longjmp
bawah callstack (lihat komentar dari MikeMB, meskipun saya belum memiliki kesempatan untuk memverifikasi bahwa).#include <stdio.h> #include <setjmp.h> jmp_buf bufferA, bufferB; void routineB(); // forward declaration void routineA() { int r ; printf("(A1)\n"); r = setjmp(bufferA); if (r == 0) routineB(); printf("(A2) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20001); printf("(A3) r=%d\n",r); r = setjmp(bufferA); if (r == 0) longjmp(bufferB, 20002); printf("(A4) r=%d\n",r); } void routineB() { int r; printf("(B1)\n"); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10001); printf("(B2) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10002); printf("(B3) r=%d\n", r); r = setjmp(bufferB); if (r == 0) longjmp(bufferA, 10003); } int main(int argc, char **argv) { routineA(); return 0; }
Gambar berikut menunjukkan aliran eksekusi:
Catatan peringatan
Saat menggunakan setjmp / longjmp ketahuilah bahwa mereka memiliki efek pada validitas variabel lokal yang sering tidak dipertimbangkan.
Cf. pertanyaan saya tentang topik ini .
sumber
setjmp
sebelum Andalongjmp
. Ini tidak standar.routineA
danroutineB
menggunakan tumpukan yang sama, ini hanya berfungsi untuk coroutine yang sangat primitif. JikaroutineA
panggilan yang sangat bersarangroutineC
setelah panggilan pertama keroutineB
dan iniroutineC
berjalanroutineB
sebagai coroutine, makaroutineB
bahkan mungkin menghancurkan tumpukan kembali (tidak hanya variabel lokal) dariroutineC
. Jadi tanpa mengalokasikan tumpukan eksklusif (melaluialloca()
setelah meneleponrountineB
?) Anda akan mendapatkan masalah serius dengan contoh ini jika digunakan sebagai resep.Teorinya adalah Anda dapat menggunakannya untuk penanganan error sehingga Anda dapat keluar dari call chain yang sangat bertingkat tanpa perlu menangani error penanganan di setiap fungsi dalam chain.
Seperti setiap teori pintar, ini berantakan ketika bertemu dengan kenyataan. Fungsi perantara Anda akan mengalokasikan memori, mengambil kunci, membuka file, dan melakukan semua jenis hal berbeda yang memerlukan pembersihan. Jadi dalam praktiknya
setjmp
/longjmp
biasanya merupakan ide yang buruk kecuali dalam keadaan yang sangat terbatas di mana Anda memiliki kendali penuh atas lingkungan Anda (beberapa platform tertanam).Dalam pengalaman saya dalam banyak kasus kapan pun Anda berpikir bahwa menggunakan
setjmp
/longjmp
akan berfungsi, program Anda jelas dan cukup sederhana sehingga setiap panggilan fungsi perantara dalam rantai panggilan dapat melakukan penanganan kesalahan, atau sangat berantakan dan tidak mungkin untuk memperbaikinya yang harus Anda lakukanexit
saat Anda melakukannya. mengalami kesalahan.sumber
libjpeg
. Seperti di C ++, sebagian besar kumpulan rutinitas C mengambil astruct *
untuk beroperasi pada sesuatu sebagai kolektif. Alih-alih menyimpan alokasi memori fungsi menengah Anda sebagai penduduk lokal, mereka dapat disimpan dalam struktur. Ini memungkinkan seoranglongjmp()
penangan untuk membebaskan memori. Juga, ini tidak memiliki begitu banyak tabel pengecualian yang gagal sehingga semua kompiler C ++ masih menghasilkan 20 tahun setelah fakta.Like every clever theory this falls apart when meeting reality.
Memang, alokasi sementara dan sejenisnya membuatlongjmp()
rumit, karena Anda kemudian harussetjmp()
beberapa kali dalam tumpukan panggilan (sekali untuk setiap fungsi yang perlu melakukan semacam pembersihan sebelum keluar, yang kemudian perlu "memunculkan kembali pengecualian" denganlongjmp()
konteks yang awalnya diterima). Ini menjadi lebih buruk jika sumber daya tersebut diubah setelahsetjmp()
, karena Anda harus mendeklarasikannyavolatile
untuk mencegahlongjmp()
dari clobber mereka.Kombinasi
setjmp
danlongjmp
adalah "kekuatan supergoto
". Gunakan dengan hati-hati EXTREME. Namun, seperti yang dijelaskan orang lain, alongjmp
sangat berguna untuk keluar dari situasi kesalahan yang parah, ketika Anda inginget me back to the beginning
cepat, daripada harus menuangkan kembali pesan kesalahan untuk 18 lapisan fungsi.Namun, sama seperti
goto
, tetapi lebih buruk, Anda harus BENAR-BENAR berhati-hati dalam menggunakan ini. Alongjmp
hanya akan membuat Anda kembali ke awal kode. Ini tidak akan memengaruhi semua status lain yang mungkin telah berubah antarasetjmp
dan kembali kesetjmp
awal. Jadi alokasi, kunci, struktur data setengah diinisialisasi, dll, masih dialokasikan, terkunci dan setengah diinisialisasi ketika Anda kembali ke tempatsetjmp
dipanggil. Ini berarti, Anda harus benar-benar memperhatikan tempat Anda melakukan ini, bahwa BENAR-BENAR boleh meneleponlongjmp
tanpa menyebabkan LEBIH BANYAK masalah. Tentu saja, jika hal berikutnya yang Anda lakukan adalah "reboot" [setelah menyimpan pesan tentang kesalahan, mungkin] - dalam sistem tertanam di mana Anda telah menemukan bahwa perangkat keras dalam keadaan buruk, misalnya, baiklah.Saya juga telah melihat
setjmp
/longjmp
digunakan untuk menyediakan mekanisme penguliran yang sangat dasar. Tapi itu kasus yang cukup istimewa - dan jelas bukan cara kerja utas "standar".Sunting: Tentu saja seseorang dapat menambahkan kode ke "menangani pembersihan", dengan cara yang sama seperti C ++ menyimpan titik pengecualian dalam kode yang dikompilasi dan kemudian mengetahui apa yang memberikan pengecualian dan apa yang perlu dibersihkan. Ini akan melibatkan semacam tabel penunjuk fungsi dan menyimpan "jika kita melompat keluar dari bawah sini, panggil fungsi ini, dengan argumen ini". Sesuatu seperti ini:
struct { void (*destructor)(void *ptr); }; void LockForceUnlock(void *vlock) { LOCK* lock = vlock; } LOCK func_lock; void func() { ref = add_destructor(LockForceUnlock, mylock); Lock(func_lock) ... func2(); // May call longjmp. Unlock(func_lock); remove_destructor(ref); }
Dengan sistem ini, Anda dapat melakukan "penanganan pengecualian lengkap seperti C ++". Tapi itu cukup berantakan, dan bergantung pada kode yang ditulis dengan baik.
sumber
setjmp
untuk menjaga setiap inisialisasi, ala C ++… dan perlu disebutkan bahwa menggunakannya untuk threading tidak standar.Karena Anda menyebutkan tertanam, saya pikir perlu diperhatikan kasus non-penggunaan : ketika standar pengkodean Anda melarangnya. Misalnya MISRA (MISRA-C: 2004: Aturan 20.7) dan JFS (AV Aturan 20): "Makro setjmp dan fungsi longjmp tidak boleh digunakan."
sumber
setjmp
danlongjmp
bisa sangat berguna dalam pengujian unit.Misalkan kita ingin menguji modul berikut:
#include <stdlib.h> int my_div(int x, int y) { if (y==0) exit(2); return x/y; }
Biasanya, jika fungsi yang akan diuji memanggil fungsi lain, Anda bisa mendeklarasikan fungsi stub untuk dipanggil yang akan meniru fungsi sebenarnya untuk menguji aliran tertentu. Namun dalam kasus ini, panggilan fungsi
exit
yang tidak kembali. Rintisan harus meniru perilaku ini.setjmp
danlongjmp
dapat melakukannya untuk Anda.Untuk menguji fungsi ini, kita dapat membuat program uji berikut:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <setjmp.h> // redefine assert to set a boolean flag #ifdef assert #undef assert #endif #define assert(x) (rslt = rslt && (x)) // the function to test int my_div(int x, int y); // main result return code used by redefined assert static int rslt; // variables controling stub functions static int expected_code; static int should_exit; static jmp_buf jump_env; // test suite main variables static int done; static int num_tests; static int tests_passed; // utility function void TestStart(char *name) { num_tests++; rslt = 1; printf("-- Testing %s ... ",name); } // utility function void TestEnd() { if (rslt) tests_passed++; printf("%s\n", rslt ? "success" : "fail"); } // stub function void exit(int code) { if (!done) { assert(should_exit==1); assert(expected_code==code); longjmp(jump_env, 1); } else { _exit(code); } } // test case void test_normal() { int jmp_rval; int r; TestStart("test_normal"); should_exit = 0; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(12,3); } assert(jmp_rval==0); assert(r==4); TestEnd(); } // test case void test_div0() { int jmp_rval; int r; TestStart("test_div0"); should_exit = 1; expected_code = 2; if (!(jmp_rval=setjmp(jump_env))) { r = my_div(2,0); } assert(jmp_rval==1); TestEnd(); } int main() { num_tests = 0; tests_passed = 0; done = 0; test_normal(); test_div0(); printf("Total tests passed: %d\n", tests_passed); done = 1; return !(tests_passed == num_tests); }
Dalam contoh ini, Anda menggunakan
setjmp
sebelum memasukkan fungsi untuk menguji, kemudian di stubbedexit
Anda memanggillongjmp
untuk kembali langsung ke kasus pengujian Anda.Perhatikan juga bahwa redefined
exit
memiliki variabel khusus yang diperiksa untuk melihat apakah Anda benar-benar ingin keluar dari program dan memanggil_exit
untuk melakukannya. Jika Anda tidak melakukan ini, program pengujian Anda mungkin tidak berhenti dengan bersih.sumber
Saya telah menulis mekanisme penanganan pengecualian seperti Java di C menggunakan
setjmp()
,longjmp()
dan fungsi sistem. Ini menangkap pengecualian khusus tetapi juga memberi sinyal sukaSIGSEGV
. Ini menampilkan blok penanganan pengecualian bersarang tak terbatas, yang bekerja di seluruh panggilan fungsi, dan mendukung dua implementasi threading yang paling umum. Ini memungkinkan Anda untuk menentukan hierarki hierarki kelas pengecualian yang menampilkan pewarisan waktu tautan, dancatch
pernyataan berjalan di pohon ini untuk melihat apakah perlu menangkap atau meneruskan.Berikut adalah contoh tampilan kode menggunakan ini:
try { *((int *)0) = 0; /* may not be portable */ } catch (SegmentationFault, e) { long f[] = { 'i', 'l', 'l', 'e', 'g', 'a', 'l' }; ((void(*)())f)(); /* may not be portable */ } finally { return(1 / strcmp("", "")); }
Dan inilah bagian dari file include yang mengandung banyak logika:
#ifndef _EXCEPT_H #define _EXCEPT_H #include <stdlib.h> #include <stdio.h> #include <signal.h> #include <setjmp.h> #include "Lifo.h" #include "List.h" #define SETJMP(env) sigsetjmp(env, 1) #define LONGJMP(env, val) siglongjmp(env, val) #define JMP_BUF sigjmp_buf typedef void (* Handler)(int); typedef struct _Class *ClassRef; /* exception class reference */ struct _Class { int notRethrown; /* always 1 (used by throw()) */ ClassRef parent; /* parent class */ char * name; /* this class name string */ int signalNumber; /* optional signal number */ }; typedef struct _Class Class[1]; /* exception class */ typedef enum _Scope /* exception handling scope */ { OUTSIDE = -1, /* outside any 'try' */ INTERNAL, /* exception handling internal */ TRY, /* in 'try' (across routine calls) */ CATCH, /* in 'catch' (idem.) */ FINALLY /* in 'finally' (idem.) */ } Scope; typedef enum _State /* exception handling state */ { EMPTY, /* no exception occurred */ PENDING, /* exception occurred but not caught */ CAUGHT /* occurred exception caught */ } State; typedef struct _Except /* exception handle */ { int notRethrown; /* always 0 (used by throw()) */ State state; /* current state of this handle */ JMP_BUF throwBuf; /* start-'catching' destination */ JMP_BUF finalBuf; /* perform-'finally' destination */ ClassRef class; /* occurred exception class */ void * pData; /* exception associated (user) data */ char * file; /* exception file name */ int line; /* exception line number */ int ready; /* macro code control flow flag */ Scope scope; /* exception handling scope */ int first; /* flag if first try in function */ List * checkList; /* list used by 'catch' checking */ char* tryFile; /* source file name of 'try' */ int tryLine; /* source line number of 'try' */ ClassRef (*getClass)(void); /* method returning class reference */ char * (*getMessage)(void); /* method getting description */ void * (*getData)(void); /* method getting application data */ void (*printTryTrace)(FILE*);/* method printing nested trace */ } Except; typedef struct _Context /* exception context per thread */ { Except * pEx; /* current exception handle */ Lifo * exStack; /* exception handle stack */ char message[1024]; /* used by ExceptGetMessage() */ Handler sigAbrtHandler; /* default SIGABRT handler */ Handler sigFpeHandler; /* default SIGFPE handler */ Handler sigIllHandler; /* default SIGILL handler */ Handler sigSegvHandler; /* default SIGSEGV handler */ Handler sigBusHandler; /* default SIGBUS handler */ } Context; extern Context * pC; extern Class Throwable; #define except_class_declare(child, parent) extern Class child #define except_class_define(child, parent) Class child = { 1, parent, #child } except_class_declare(Exception, Throwable); except_class_declare(OutOfMemoryError, Exception); except_class_declare(FailedAssertion, Exception); except_class_declare(RuntimeException, Exception); except_class_declare(AbnormalTermination, RuntimeException); /* SIGABRT */ except_class_declare(ArithmeticException, RuntimeException); /* SIGFPE */ except_class_declare(IllegalInstruction, RuntimeException); /* SIGILL */ except_class_declare(SegmentationFault, RuntimeException); /* SIGSEGV */ except_class_declare(BusError, RuntimeException); /* SIGBUS */ #ifdef DEBUG #define CHECKED \ static int checked #define CHECK_BEGIN(pC, pChecked, file, line) \ ExceptCheckBegin(pC, pChecked, file, line) #define CHECK(pC, pChecked, class, file, line) \ ExceptCheck(pC, pChecked, class, file, line) #define CHECK_END \ !checked #else /* DEBUG */ #define CHECKED #define CHECK_BEGIN(pC, pChecked, file, line) 1 #define CHECK(pC, pChecked, class, file, line) 1 #define CHECK_END 0 #endif /* DEBUG */ #define except_thread_cleanup(id) ExceptThreadCleanup(id) #define try \ ExceptTry(pC, __FILE__, __LINE__); \ while (1) \ { \ Context * pTmpC = ExceptGetContext(pC); \ Context * pC = pTmpC; \ CHECKED; \ \ if (CHECK_BEGIN(pC, &checked, __FILE__, __LINE__) && \ pC->pEx->ready && SETJMP(pC->pEx->throwBuf) == 0) \ { \ pC->pEx->scope = TRY; \ do \ { #define catch(class, e) \ } \ while (0); \ } \ else if (CHECK(pC, &checked, class, __FILE__, __LINE__) && \ pC->pEx->ready && ExceptCatch(pC, class)) \ { \ Except *e = LifoPeek(pC->exStack, 1); \ pC->pEx->scope = CATCH; \ do \ { #define finally \ } \ while (0); \ } \ if (CHECK_END) \ continue; \ if (!pC->pEx->ready && SETJMP(pC->pEx->finalBuf) == 0) \ pC->pEx->ready = 1; \ else \ break; \ } \ ExceptGetContext(pC)->pEx->scope = FINALLY; \ while (ExceptGetContext(pC)->pEx->ready > 0 || ExceptFinally(pC)) \ while (ExceptGetContext(pC)->pEx->ready-- > 0) #define throw(pExceptOrClass, pData) \ ExceptThrow(pC, (ClassRef)pExceptOrClass, pData, __FILE__, __LINE__) #define return(x) \ { \ if (ExceptGetScope(pC) != OUTSIDE) \ { \ void * pData = malloc(sizeof(JMP_BUF)); \ ExceptGetContext(pC)->pEx->pData = pData; \ if (SETJMP(*(JMP_BUF *)pData) == 0) \ ExceptReturn(pC); \ else \ free(pData); \ } \ return x; \ } #define pending \ (ExceptGetContext(pC)->pEx->state == PENDING) extern Scope ExceptGetScope(Context *pC); extern Context *ExceptGetContext(Context *pC); extern void ExceptThreadCleanup(int threadId); extern void ExceptTry(Context *pC, char *file, int line); extern void ExceptThrow(Context *pC, void * pExceptOrClass, void *pData, char *file, int line); extern int ExceptCatch(Context *pC, ClassRef class); extern int ExceptFinally(Context *pC); extern void ExceptReturn(Context *pC); extern int ExceptCheckBegin(Context *pC, int *pChecked, char *file, int line); extern int ExceptCheck(Context *pC, int *pChecked, ClassRef class, char *file, int line); #endif /* _EXCEPT_H */
Ada juga modul C yang berisi logika untuk penanganan sinyal dan beberapa pembukuan.
Sangat sulit untuk diterapkan. Saya dapat memberi tahu Anda dan saya hampir berhenti. Saya benar-benar mendorong untuk membuatnya sedekat mungkin dengan Java; Saya terkejut melihat seberapa jauh saya hanya dengan C.
Beri saya teriakan jika Anda tertarik.
sumber
main()
keluar dari pengecualian tak tertangkap. Harap beri suara positif pada jawaban ini :-)Progagation
bagian di README Saya telah memposting kode April 1999 saya ke GitHub (lihat link di jawaban yang diedit). Coba lihat; itu adalah kacang yang sulit untuk dipecahkan. Akan menyenangkan mendengar apa yang Anda pikirkan.Penggunaan setjmp / longjmp yang paling penting adalah bahwa ia bertindak sebagai "lompatan goto non-lokal". Perintah Goto (dan ada kasus langka di mana Anda perlu menggunakan goto over untuk dan sementara loop) paling aman digunakan dalam lingkup yang sama. Jika Anda menggunakan goto untuk melompati cakupan (atau lintas alokasi otomatis), kemungkinan besar Anda akan merusak tumpukan program Anda. setjmp / longjmp menghindari ini dengan menyimpan info tumpukan di lokasi yang ingin Anda lompat. Kemudian, saat Anda melompat, info tumpukan ini dimuat. Tanpa fitur ini, programmer C kemungkinan besar harus beralih ke pemrograman assembly untuk menyelesaikan masalah yang hanya bisa diselesaikan oleh setjmp / longjmp. Terima kasih Tuhan itu ada. Semua yang ada di perpustakaan C sangat penting. Anda akan tahu kapan Anda membutuhkannya.
sumber
Selain penanganan error, hal lain yang dapat Anda lakukan dan tidak disebutkan sebelumnya adalah mengimplementasikan penghitungan rektursif ekor di C dengan cara yang cerdas.
Ini sebenarnya bagaimana diimplementasikan kelanjutan dalam C tanpa mengubah kode masukan dalam gaya penerusan lanjutan.
sumber