Header <setjmp.h> menyediakan mekanisme non-local jump—melompat dari satu titik eksekusi ke titik lain yang sudah disimpan sebelumnya, bahkan melewati batas fungsi. Pasangan setjmp/longjmp sering disebut “exception handling” primitif C: setjmp menyimpan konteks eksekusi (register, stack pointer) ke variabel jmp_buf, dan longjmp memulihkan konteks tersebut—seolah-olah setjmp baru saja mengembalikan nilai. Walau berguna untuk kasus khusus seperti keluar dari deep recursion atau menangani error fatal, mekanisme ini berbahaya bila disalahgunakan karena dapat melewati pembersihan sumber daya. Pada chapter ini kita akan meninjau penggunaan dasar, simulasi exception handling, dan risiko yang harus dipahami.
Dasar: setjmp dan longjmp #
Fungsi setjmp(env) menyimpan konteks eksekusi saat ini ke env dan mengembalikan 0 saat dipanggil pertama kali. Fungsi longjmp(env, val) memulihkan konteks tersebut, sehingga eksekusi kembali ke titik setjmp—tetapi kali ini setjmp “mengembalikan” val (bila val adalah 0, otomatis diganti dengan 1). Pola umum: setjmp dipanggil di if atau switch, dengan cabang 0 untuk eksekusi normal dan cabang non-zero untuk penanganan error.
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
/* Fungsi yang melakukan longjmp kembali ke setjmp */
static void fungsi_dalam(int kode) {
printf(" di fungsi_dalam(%d): akan longjmp\n", kode);
longjmp(buf, kode); /* tidak pernah return */
printf(" (baris ini tidak akan tercetak)\n");
}
int main(void) {
printf("Sebelum setjmp\n");
/* setjmp mengembalikan 0 saat pertama dipanggil */
int rc = setjmp(buf);
if (rc == 0) {
printf("setjmp pertama kali, rc = %d\n", rc);
fungsi_dalam(42); /* panggil fungsi yang akan longjmp */
printf("(setelah fungsi_dalam, tidak tercapai)\n");
} else {
printf("setjmp kembali dari longjmp, rc = %d\n", rc);
}
printf("Program berakhir normal\n");
return 0;
}Output yang Dihasilkan:
Sebelum setjmp
setjmp pertama kali, rc = 0
di fungsi_dalam(42): akan longjmp
setjmp kembali dari longjmp, rc = 42
Program berakhir normalMari kita telaah baris-baris kode di atas:
- Baris 4 (
static jmp_buf buf;): variabel global yang menyimpan konteks eksekusi. Tipejmp_bufadalah array opaque—isi detailnya bergantung pada platform (biasanya berisi stack pointer, instruction pointer, dan register penting). - Baris 8 (
longjmp(buf, kode);): memulihkan konteks daribuf. Argumen keduakodemenjadi nilai yang dikembalikan olehsetjmpyang sesuai. Fungsilongjmptidak pernah kembali ke pemanggil—eksekusi langsung lompat kesetjmp. - Baris 14 (
int rc = setjmp(buf);): panggilan pertamasetjmpmenyimpan konteks dan mengembalikan0. Setelahlongjmpdipanggil, eksekusi kembali ke titik ini, tetapisetjmpsekarang mengembalikan42(nilai yang diberikan kelongjmp). - Baris 16 (
if (rc == 0)): cabang untuk eksekusi normal. Baris 18 (fungsi_dalam(42)) memanggil fungsi yang akan longjmp—baris 19(setelah fungsi_dalam...)tidak pernah dieksekusi. - Baris 20 (
else): cabang yang dieksekusi setelahlongjmp. Variabelrcberisi42, sesuai argumenlongjmp.
Simulasi Exception Handling #
Salah satu penggunaan utama setjmp/longjmp adalah mensimulasikan exception handling ala C++ atau Java: fungsi dalam yang menemui error dapat “melempar” exception dengan longjmp, dan pemanggil menangkapnya dengan setjmp. Pola ini umum di pustaka C klasik seperti libpng atau beberapa implementasi regular expression.
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
/* Simulasi exception handling untuk pembagian */
typedef struct {
jmp_buf env;
const char *pesan;
} error_context;
/* Fungsi bagi yang "throw" bila divisor nol */
static int bagi_dengan_error(int a, int b, error_context *ctx) {
if (b == 0) {
ctx->pesan = "Pembagian dengan nol!";
longjmp(ctx->env, 1); /* throw */
}
return a / b;
}
int main(void) {
error_context ctx;
int kasus[][2] = { {100, 5}, {84, 0}, {99, 3} };
size_t n = sizeof(kasus) / sizeof(kasus[0]);
for (size_t i = 0; i < n; i++) {
/* setjmp mengembalikan 0 pertama kali, non-zero setelah longjmp */
if (setjmp(ctx.env) == 0) {
int hasil = bagi_dengan_error(kasus[i][0], kasus[i][1], &ctx);
printf("%d / %d = %d\n", kasus[i][0], kasus[i][1], hasil);
} else {
printf("%d / %d gagal: %s\n",
kasus[i][0], kasus[i][1], ctx.pesan);
}
}
return 0;
}Output yang Dihasilkan:
100 / 5 = 20
84 / 0 gagal: Pembagian dengan nol!
99 / 3 = 33Struktur error_context memuat jmp_buf env dan pesan error. Pada setiap iterasi loop, setjmp(ctx.env) dipanggil—mengembalikan 0 saat pertama, lalu pemanggilan normal bagi_dengan_error. Bila b == 0, fungsi mengisi ctx.pesan dan memanggil longjmp(ctx.env, 1)—eksekusi kembali ke setjmp, kali ini mengembalikan 1. Cabang else mencetak pesan error. Loop kemudian lanjut ke iterasi berikutnya—setjmp dipanggil lagi untuk konteks baru. Pola ini menghindari pengecekan if (b == 0) di setiap tingkat pemanggil—error dari kedalaman berapapun langsung ditangkap di satu titik.
Risiko dan Batasan #
setjmp/longjmp adalah fitur low-level yang berbahaya bila disalahgunakan. Risiko utama: melewati destructor/cleanup. Saat longjmp dilompat, stack frame fungsi yang dilewati tidak di-unwind dengan rapi—variabel lokal tidak dibersihkan, file yang dibuka tidak ditutup, memori yang dialokasikan tidak di-free. Untuk program C murni ini mungkin tidak terlalu parah (kompiler tidak menjalankan destructor otomatis), tetapi konsekuensinya nyata: resource leak.
#include <stdio.h>
#include <setjmp.h>
#include <stdlib.h>
static jmp_buf env;
static void fungsi_bermasalah(void) {
/* Alokasi memori */
char *buf = malloc(1024);
if (buf == NULL) return;
printf("Memori dialokasikan\n");
/* Simulasi error - lompat tanpa free */
longjmp(env, 1);
/* free(buf) di bawah TIDAK PERNAH dieksekusi - memory leak! */
free(buf);
printf("Memori dibebaskan (tidak tercapai)\n");
}
int main(void) {
if (setjmp(env) == 0) {
fungsi_bermasalah();
} else {
printf("Error ditangkap\n");
}
printf("Program selesai (tapi memori bocor)\n");
return 0;
}Output yang Dihasilkan:
Memori dialokasikan
Error ditangkap
Program selesai (tapi memori bocor)Fungsi fungsi_bermasalah mengalokasikan 1024 byte dengan malloc, lalu memanggil longjmp tanpa memanggil free(buf). Karena longjmp langsung lompat ke setjmp di main, baris free(buf) di akhir fungsi tidak pernah dieksekusi—memory leak 1024 byte. Inilah masalah utama dengan setjmp/longjmp: tidak ada automatic cleanup. Bandingkan dengan try/catch di C++ yang menjalankan destructor setiap objek lokal saat stack unwinding. Di C, kita harus manual memastikan semua resource sudah dibebaskan sebelum longjmp.