بينما كان يكتب هذه المقالة التقنية البحتة ، تمكنت هبر من التحول إلى مكتب محلي لمنظمة الصحة العالمية ، والآن أشعر بالخجل من نشره ... لكن الأمل يسخن في روح أن موظفي تكنولوجيا المعلومات لم يهربوا بعد وستجد قارئها. أم لا؟
لطالما أعجبت بمكتبة C القياسية ، و C نفسها - بكل بساطتها ، لا تزال تهب روح هؤلاء لأول مرة ذو عيون حمراء قراصنة . في مسودة المعيار الرسمي الأول (ANSI C، ويعرف أيضا باسم C89، الملقب ANS X3.159-1989، في وقت لاحق، C90 و IEC 9899: 1990) يحدد 145 وظائف وحدات الماكرو، منها حوالي 25 اختلافات (بسبب عدم وجود في لغة الزائد) ، و 26 رياضيات بحتة. يستشهد K&R في الإصدار الثاني ² بـ 114 وظيفة (بالإضافة إلى الرياضيات) ، معتبرا أن الباقي غريب. يوجد في المسودة C11 بالفعل 348 وظيفة ، ولكن أكثر من مائة منها رياضيات ، و 90 أخرى هي "حمولة زائدة". الآن دعونا نلقي نظرة على Boost ، حيث يوجد 160 مكتبة فقط . تبا لي ...
ومن بين هذه المئات ونصف الوظائف ، كانت هناك دائمًا: معالجة الإشارات والوظائف المتنوعة (التي وصلت إلى تفسير PHP بعد 25 عامًا ، وفي دلفي ، التي كانت تزدهر في وقت واحد ، لا تزال مفقودة) وحوالي 50 وظيفة سلسلة مثل printf () ( mmm ... JavaScript) ، strftime () (...) و scanf () (بديل رخيص للنظامي).
وكان هناك دائمًا setjmp () / longjmp () ، مما يسمح لك بتنفيذ آلية الاستثناء المألوفة بلغات أخرى ، دون تجاوز نطاق المحمولة C. إليك حديث عنهم - عالم الزلازل ، المداخن ، السجلات ، المجمعات وغيرها من العتاد ، والكرز هو إحصاء غريب ( المفسد : Visual Studio ليس ثابتًا ، كأرنب مارس ، ويرمي saneex.c في نصف الوقت على الإطلاق).

نص مخفي¹ .
² , . 270 , 80 — . , . K&R — , .
³ , ANSI ISO , . .
⁴ , «» TinyURL, URL , і . , -. urlex.org.
 جدول المحتويات:
,  — setjmp()/longjmp(), setjmp.h, «SJLJ» ( , ). C89 , -, , (  — ,  — ).
, , , zzeng. , , , ¹, , , 
¹ CException  — 60 , , , ANSI C, finally , .
,  — , , , , , « ». (, , - «» , errno .  — , .)
, :
- , - , ( ,  — , if (error) return -1;)
- -  — , ,
. , .
setjmp()/longjmp()
, longjmp() — goto, setjmp() — 
, , , , setjmp.h ^W ? , setjmp() , (, , ). , 
, , setjmp() — fork() POSIX . , *nix’ API WinAPI,  — , .  — « , ?»… .
, ,  — , () .  — ( Intel’ — ),  — ( Intel’ — -, ). :
void sub(int s) {
  char buf[256];
  sub(2);
}
int main(int m) {
  sub(1);
}
 — tcc (Tiny C Compiler) - . . tcc . sub() ( Intel, ):
sub     esp, 100h       ;     
mov     eax, 2          ;  
push    eax
call    sub_401000      ;  sub()
add     esp, 4          ;     (= cdecl)
:

 — ( Intel… , ). ESP (RSP x86_64). setjmp() ESP/RSP, , jmp_buf, . longjmp() ( ) — , , setjmp(), (). , «undo» (, ).
setjmp() jmp FEF8h (FDF0h .. — ) , :
void sub(int s) {
  char buf[256];
  jmp_buf jmp;
  setjmp(jmp);
  sub(2);
}
, , ™:
-  — , , ! — , , , setjmp() , longjmp() , setjmp()
, -, clobbering
. :
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
int main(void) {
  int i;
  jmp_buf jmp;
  i = rand();
  if (setjmp(jmp) == 0) {
    i = rand();
    printf("%d\n", i);
    longjmp(jmp, 1);
  } else {
    printf("%d\n", i);
  }
}
: ?
: . -!
, gcc. -O0, , :
; int main(void) {
  push    ebp             ;  ( stack frame)
  mov     ebp, esp        ; EBP     ESP (  )
  sub     esp, E0h
  ...
  call    _rand           ;    EAX
  mov     [ebp-D4h], eax  ;  i = rand();  i   (EBP-D4h)
  ...
; if (... == 0) {         ;  setjmp()      
  call    _rand
  mov     [ebp-D4h], eax  ;  i = rand();  
; printf("%d\n", i);
  mov     eax, [ebp-D4h]  ;  i    
  mov     esi, eax
  lea     edi, format     ;   "%d\n"
  mov     eax, 0
  call    _printf
  ...
; } else {                ;    setjmp()  
  mov     eax, [ebp-D4h]  ;   i,    
  mov     esi, eax
  lea     edi, format     ; "%d\n"
  mov     eax, 0
  call    _printf
, i ( EBP - D4h). , :
- 256 char int jmp_buf, 4 200 , 20 - , 224 (E0h) 100h, 
- ESP setjmp() FFF8h - E0h = FF18h(FEF8h), jmp
 
- i, i ( FF18h)
- longjmp() FF18h, , i , - , (jmp), main() ( )
 
-O1, :
;   stack frame  ,   ESP 
  sub     esp, E8h
  ...
  call    _rand
  mov     [esp+E8h-DCh], eax  ; i = rand();  ,    -O0
  ...
; -O1 - ,   else  , 
; if (setjmp() == 0) ( - ),  
;  ;       
; if (... == 0) {
  call    _rand
  mov     esi, eax            ; !  i  
; printf("%d\n", i);
  lea     edi, format     ; "%d\n"
  mov     eax, 0
  call    _printf
  ...
; } else {
  mov     esi, [esp+E8h-DCh]  ; !  i  
  lea     edi, format     ; "%d\n"
  mov     eax, 0
  call    _printf
, -O1 gcc :
test.c:6:11: warning: variable ‘i’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Wclobbered]
? i , ( if) gcc, i printf(), ESI, ( ESI printf(), . ABI, . 22 — RDI (format), RSI (i), …). - :
- ESP + E8h - DChrand()
- ESI
- printf() ( ) ()
- longjmp() , ,
- printf() ( else) , , ,
 - ESI, (, printf() longjmp())
 
, :
stack[i] = rand();          // i = rand();   (1)
if (setjmp(jmp) == 0) {
  ESI = rand();             // i = rand();   (2)
  printf("%d\n", ESI);      //   (2)
  longjmp(jmp, 1);          // 
} else {
  printf("%d\n", stack[i]); //   (1)
  //     :
  printf("%d\n", ESI);      //  ,   -
                            // "" ( printf()  longjmp())
}
, , gcc rand() ESI ( -O3). SO , x86_64 ( ) , EAX. ? , gcc printf() else longjmp(), rand() printf() — , i .
 — .
volatile
« » — volatile ( — «»). , , , :
volatile int i;
-O1 if:
; :
  call    _rand
  mov     esi, eax
; :
  call    _rand
  mov     [rsp+E8h-DCh], eax
  mov     esi, [rsp+E8h-DCh]
;    :
  call    _rand
  mov     esi, eax
  mov     [rsp+E8h-DCh], eax
, ():
if (setjmp(jmp) == 0) {
  ESI = stack[i] = rand();
IRL
,  — -volatile , SJLJ .  — , SJLJ 
, ,  — , « », , , - (). , DrMefistO.
Quake World WinMain(), jmp_buf, , « continue»:
// WinQuake/host.c
jmp_buf         host_abortserver;
void Host_EndGame (char *message, ...)
{
  ...
  if (cls.demonum != -1)
    CL_NextDemo ();
  else
    CL_Disconnect ();
  longjmp (host_abortserver, 1);
}
void Host_Error (char *error, ...)
{
  ...
  if (cls.state == ca_dedicated)
    Sys_Error ("Host_Error: %s\n",string);  // dedicated servers exit
  CL_Disconnect ();
  cls.demonum = -1;
  inerror = false;
  longjmp (host_abortserver, 1);
}
void _Host_Frame (float time)
{
  static double           time1 = 0;
  static double           time2 = 0;
  static double           time3 = 0;
  int                     pass1, pass2, pass3;
  if (setjmp (host_abortserver) )
    return;                 // something bad happened, or the server disconnected
  ...
}
// QW/client/sys_win.c
int WINAPI WinMain (...)
{
  ...
  while (1)
  {
    ...
    newtime = Sys_DoubleTime ();
    time = newtime - oldtime;
    Host_Frame (time);
    oldtime = newtime;
  }
  /* return success of application */
  return TRUE;
}
,  — . , setjmp() glibc , . :
- , , SJLJ/saneex.c
- -…- (, ) , -  — ,
- ,  — zero-cost exceptions (, , zero-cost try), try, () — goto , , , «» , -,
«» zero-cost exceptions , volatile-, , ( longjmp()). , :
, saneex.c zero-cost (  — ), setjmp(), ? , ?  — .
« », main() 100 try/catch throw().
C:
#include <stdio.h>
#include <time.h>
#include "saneex.h"
int main(void) {
  for (int i = 0; i < 100000; i++) {
    try {
      //  ("" = ):
      throw(msgex("A quick fox jumped over a red dog and a nyancat was spawned"));
      //  ("" = ):
      time(NULL);
    } catchall {
      fprintf(stderr, "%s\n", curex().message);
    } endtry
  }
}
++ ( , cerr << fprintf()):
#include <iostream>
#include <vector>
#include <stdexcept>
#include <time.h>
int main() {
  std::vector<int> vec{ 3, 4, 3, 1 };
  for (int i = 0; i < 100000; i++) {
    try {
      //  ("" = ):
      int i{ vec.at(4) };
      //  ("" = ):
      time(NULL);
    }
    catch (std::out_of_range & e) {
      // <<  fprintf()     25-50%
      //std::cerr << "Accessing a non-existent element: " << e.what() << '\n';
      fprintf(stderr, "%s\n", e.what());
    }
    catch (std::exception & e) {
      //std::cerr << "Exception thrown: " << e.what() << '\n';
      fprintf(stderr, "%s\n", e.what());
    }
    catch (...) {
      //std::cerr << "Some fatal error\n";
      fprintf(stderr, "Some fatal error");
    }
  }
  return 0;
}
( 64-):
- Windows 10 2019 LTSC PowerShell Measure-Command { test.exe 2>$null }
- Live CD Ubuntu time
Windows __try/__except, :
#include <windows.h>
#include <stdio.h>
#include <vector>
int filterExpression(EXCEPTION_POINTERS* ep) {
  ep->ContextRecord->Eip += 8;
  return EXCEPTION_EXECUTE_HANDLER;
}
int main() {
  static int zero;
  for (int i = 0; i < 100000; i++) {
    __try {
      zero = 1 / zero;
      __asm {
        nop
        nop
        nop
        nop
        nop
        nop
        nop
      }
      printf("Past the exception.\n");
    }
    __except (filterExpression(GetExceptionInformation())) {
      printf("Handler called.\n");
    }
  }
}
 — , :
error C2712: Cannot use __try in functions that require object unwinding
, , . 1100-1300 (Debug Release, x86) — , VS, , g++.
№                     ()¹           saneex 
1.  VS 2019 v16.0.0   Debug     x64   saneex.c        9713  / 8728  = 1.1    1.8 / 1.8
2.  VS 2019 v16.0.0   Debug     x64   saneex.c       95    / 46    = 2      4.5 / 2.3
3.  VS 2019 v16.0.0   Debug     x64   C++             5449  / 4750² = 1.6
4.  VS 2019 v16.0.0   Debug     x64   C++            21    / 20    = 1
5.  VS 2019 v16.0.0   Release   x64   saneex.c        8542³ / 182   = 47     1.8 / 0.4
6.  VS 2019 v16.0.0   Release   x64   saneex.c       80³   / 23    = 3.5    8   / 1.8
7.  VS 2019 v16.0.0   Release   x64   C++             4669³ / 420   = 11
8.  VS 2019 v16.0.0   Release   x64   C++            10³   / 13    = 0.8
9.  gcc 9.2.1         -O0       x64   saneex.c        71    / 351   = 0.2    0.2 / 0.6
10. gcc 9.2.1         -O0       x64   saneex.c       6     / 39    = 0.2    1.5 / 1.1
11. g++ 9.2.1         -O0       x64   C++             378   / 630   = 0.6
12. g++ 9.2.1         -O0       x64   C++            4     / 37    = 0.1
13. gcc 9.2.1         -O3       x64   saneex.c        66    / 360   = 0.2    0.2 / 0.6
14. gcc 9.2.1         -O3       x64   saneex.c       5     / 23    = 0.2    1   / 0.6
15. g++ 9.2.1         -O3       x64   C++             356   / 605   = 0.6
16. g++ 9.2.1         -O3       x64   C++            5     / 38    = 0.1
¹ Windows 7 SP1 x64 VS 2017 v15.9.17 gcc cygwin.
² : fprintf() cerr <<, 3 : 1386/1527 .
³ VS , .
… :
- / «» VS.  — .
- cerr <<fprintf() VS 3-4 ( 3). ?
- try throw — (4-28 100 ).
- «» Debug VS, saneex.c, ( 2.3 VS, 5 gcc/g++), try throw — , . !
… . !
use-case — try throw (« , »), setjmp(), , , , . , , setjmp() OpenBSD (1.45) — Solaris. 2005 . «» — , .
, …
 — saneex.c
, :
- Visual Studio
- , throw() , finally catch ( )
- ( static)
- - (__thread/_Thread_local)
- public domain (CC0)
GitHub. , . saneex-demo.c :
01.    #include <stdio.h>
02.    #include "saneex.h"
03.
04.    int main(void) {
05.      sxTag = "SaneC's Exceptions Demo";
06.
07.      try {
08.        printf("Enter a message to fail with: [] [1] [2] [!] ");
09.
10.        char msg[50];
11.        thrif(!fgets(msg, sizeof(msg), stdin), "fgets() error");
12.
13.        int i = strlen(msg) - 1;
14.        while (i >= 0 && msg[i] <= ' ') { msg[i--] = 0; }
15.
16.        if (msg[0]) {
17.          errno = atoi(msg);
18.          struct SxTraceEntry e = newex();
19.          e = sxprintf(e, "Your message: %s", msg);
20.          e.uncatchable = msg[0] == '!';
21.          throw(e);
22.        }
23.
24.        puts("End of try body");
25.
26.      } catch (1) {
27.        puts("Caught in catch (1)");
28.        sxPrintTrace();
29.
30.      } catch (2) {
31.        puts("Caught in catch (2)");
32.        errno = 123;
33.        rethrow(msgex("calling rethrow() with code 123"));
34.
35.      } catchall {
36.        printf("Caught in catchall, message is: %s\n", curex().message);
37.
38.      } finally {
39.        puts("Now in finally");
40.
41.      } endtry
42.
43.      puts("End of main()");
44.    }
, :
End of try body
Now in finally
End of main()
- , , (1), catch (1)(26.), :
Caught in catch (1)
Your message: 1 hello, habr!
    ...at saneex-demo.c:18, code 1
Now in finally
End of main()
Caught in catch (2)
Now in finally
Uncaught exception (code 123) - terminating. Tag: SaneC's Exceptions Demo
Your message: 2 TM! kak tam blok4ain?
    ...at saneex-demo.c:18, code 2
calling rethrow() with code 123
    ...at saneex-demo.c:33, code 123
rethrown by ENDTRY
    ...at saneex-demo.c:41, code 123
- !, «» (uncatchable; 20.) — try , ( catch, finally),  — abort():
Caught in catch (1)
Your message: ! it is a good day to die
    ...UNCATCHABLE at saneex-demo.c:18, code 0
Now in finally
Uncaught exception (code 0) - terminating. Tag: SaneC's Exceptions Demo
Your message: ! it is a good day to die
    ...UNCATCHABLE at saneex-demo.c:18, code 0
UNCATCHABLE rethrown by ENDTRY
    ...at saneex-demo.c:41, code 0
Caught in catchall, message is: Your message: 3 we need more gold
Now in finally
End of main()
«»
. , ( MSVC¹), C11 (TLS):
#define SX_THREAD_LOCAL _Thread_local
¹ Microsoft - open source, 
sxTag (05.) — , stderr.  — (__DATE__ __TIME__).
SxTraceEntry ( stack trace).  — (struct SxTraceEntry) {...}:
- newex()— ; __FILE__, __LINE__ = errno ( , fgets(); 11.)
 - 1 1 ( setjmp() 0 ), catch (0)
 
- msgex(m)— newex(), ( )
- exex(m, e)— msgex(), ; free() :
try {
  TimeoutException *e = malloc(sizeof(*e));
  e->elapsed = timeElapsed;
  e->limit = MAX_TIMEOUT;
  errno = 146;
  throw(exex("Connection timed out", e));
} catch (146) {
  printf("%s after %d\n", curex().message,
    //   void *SxTraceEntry.extra:
    ((TimeoutException *) curex().extra)->elapsed);
} endtry
, , designated initializers C99 ( Visual Studio 2013+):
throw( (struct SxTraceEntry) {.message = "kaboom!"} );
:
- throw(e)— SxTraceEntry
- rethrow(e)— throw(), stack trace; catch/catchall
- thrif(x, m)— ;- if (x)SxTraceEntry x + m «»
- thri(x)— thrif(), m
«»  — fgets() (11.), . fgets() ( EOF: ./a.out </dev/null), . :
thri(read(0xBaaD, buf, nbyte));
// errno = 9, "Bad file descriptor"
// Assertion error: read(0xBaaD, buf, nbyte);
… « »
( !):
- endtry — ( try)
 
- return try endtry — , ; PR
 
«», volatile. «»  — (. longjmp()), , try, catch/catchall/finally endtry, volatile. . :
int foo = 1;
try {
  foo = 2;
  //    foo
} catchall {
  //    !
} finally {
  //   !
} endtry
//   !
volatile :
volatile int foo = 1;
try {
  ...
:
- () :
- struct SxTryContext— try,  — , jmp_buf ; , :
try {
  try {
    //  
  } endtry
} endtry
- struct SxTraceEntry— stack trace, , ; , try:
try {         //  SxTryContext
  try {       //  SxTryContext
              //  SxTraceEntry
    throw(msgex(" !"));
              //  SxTraceEntry
  } catchall {
              //  SxTraceEntry
    rethrow(msgex("   !"));
              //  SxTraceEntry (*)
  } endtry
} endtry
rethrow() throw(), SxTraceEntry (*) ,  — (stack trace ). , sxAddTraceEntry(e).
حاول والعناصر الهيكلية الأخرى هي وحدات الماكرو (- K.O. ). الأقواس { }بعدها اختيارية. في النهاية ، كل ذلك يتلخص في الرمز الزائف التالي:
try {                             int _sxLastJumpCode = setjmp(add_context()¹);
                                  bool handled = false;
                                  if (_sxLastJumpCode == 0) {
  throw(msgex("Mama mia!"));        clearTrace();
                                    sxAddTraceEntry(msgex(...));
                                    if (count_contexts() == 0) {
                                      fprintf(stderr, "Shurik, vs propalo!");
                                      sxPrintTrace();
                                      exit(curex().code);
                                    } else {
                                      longjmp(top_context());
                                    }
} catch (9000) {                  } else if (_sxLastJumpCode == 9000) {
                                    handled = true;
} catchall {                      } else {
                                    handled = true;
} finally {                       }
                                  //    finally { }
} endtry                          remove_context();
                                  if (!handled) {
                                    //    throw()
                                  }
¹ الأسماء التي تحتوي على _ غير مستخدمة في المكتبة ، إنها مجردة.
أعتقد أنه بعد التوضيحات التفصيلية لكيفية عمل SJLJ ، هناك شيء آخر غير ضروري للتعليق هنا ، لذا دعني أخذ إجازة وأعطيك الكلمة بالفعل.