Ketika menulis artikel yang murni teknis ini, Habr berhasil berubah menjadi kantor lokal WHO dan sekarang saya bahkan malu untuk mempublikasikannya ... tapi harapannya menghangatkan jiwa bahwa orang-orang IT belum melarikan diri dan dia akan menemukan pembacanya. Atau tidak?
Saya selalu dikagumi oleh pustaka C standar, dan C itu sendiri - dengan semua minimalisnya, ia masih meniupkan semangat mereka yang pertama kali mata merah hacker . The draft standar resmi pertama (ANSI C, alias C89, alias ANS X3.159-1989, kemudian, C90 dan IEC 9899: 1990) mendefinisikan 145 fungsi dan macro, dimana sekitar 25 variasi (karena kurangnya dalam bahasa kelebihan beban), dan 26 adalah murni matematika. K&R dalam edisi kedua ² mengutip 114 fungsi (ditambah matematika), menganggap sisanya sebagai eksotis. Dalam konsep C11 sudah ada 348 fungsi, tetapi lebih dari seratus adalah matematika, dan 90 lainnya adalah "kelebihan". Sekarang mari kita lihat Boost, di mana hanya ada 160 perpustakaan . Chur aku ...
Dan di antara fungsi seratus setengah ini selalu ada: pemrosesan sinyal, fungsi variasional (yang mencapai ditafsirkan PHP 25 tahun kemudian, dan dalam Delphi, yang booming pada satu waktu, mereka masih hilang) dan sekitar 50 fungsi string seperti printf () ( mmm ... JavaScript), strftime () (...) dan scanf () (alternatif murah untuk pelanggan tetap).
Dan selalu ada setjmp () / longjmp () , yang memungkinkan Anda untuk menerapkan mekanisme pengecualian yang akrab dalam bahasa lain, tanpa melampaui ruang lingkup portabel C. Inilah pembicaraan tentang mereka - Dunia Gempa , tumpukan, register, perakit, dan material lainnya , dan ceri adalah statistik yang aneh ( spoiler : Visual Studio tidak konstan, seperti kelinci Maret, dan membuang saneex.c
separuh waktu semua).

Teks tersembunyi¹ .
² , . 270 , 80 — . , . K&R — , .
³ , ANSI ISO , . .
⁴ , «» TinyURL, URL , і . , -. urlex.org.
Daftar Isi:
, — setjmp()/longjmp(), setjmp.h, «SJLJ» ( , ). C89 , -, , ( — , — ).
, , , zzeng. , , , ¹, , , ( , ), .
¹ CException — 60 , , , ANSI C, finally , .
, — , , , , , « ». (, , - «» , errno . — , .)
, :
- , - , ( , — ,
if (error) return -1;
) - - — , ,
. , .
setjmp()/longjmp()
, longjmp() — goto, setjmp() — goto run-time. , «goto ». , , , goto, — , goto . , - , ( — «try»).
, , , , 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 - DCh
rand()- 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, , , 8 , .
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)
— SxTraceEntryrethrow(e)
— throw(), stack trace; catch/catchallthrif(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)
.
coba dan elemen struktural lainnya adalah makro (- K.O Anda ). Kurung { }
setelahnya adalah opsional. Pada akhirnya, semuanya bermuara pada kode-pseudo berikut:
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()
}
¹ Nama-nama dengan _ tidak digunakan di perpustakaan, itu adalah abstraksi.
Saya pikir, setelah penjelasan terperinci tentang cara kerja SJLJ, sesuatu yang lain tidak perlu dikomentari di sini, jadi izinkan saya mengambil cuti dan memberikan dasar bagi Anda.