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.

c
#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:

text
Sebelum setjmp
setjmp pertama kali, rc = 0
  di fungsi_dalam(42): akan longjmp
setjmp kembali dari longjmp, rc = 42
Program berakhir normal

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (static jmp_buf buf;): variabel global yang menyimpan konteks eksekusi. Tipe jmp_buf adalah array opaque—isi detailnya bergantung pada platform (biasanya berisi stack pointer, instruction pointer, dan register penting).
  2. Baris 8 (longjmp(buf, kode);): memulihkan konteks dari buf. Argumen kedua kode menjadi nilai yang dikembalikan oleh setjmp yang sesuai. Fungsi longjmp tidak pernah kembali ke pemanggil—eksekusi langsung lompat ke setjmp.
  3. Baris 14 (int rc = setjmp(buf);): panggilan pertama setjmp menyimpan konteks dan mengembalikan 0. Setelah longjmp dipanggil, eksekusi kembali ke titik ini, tetapi setjmp sekarang mengembalikan 42 (nilai yang diberikan ke longjmp).
  4. 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.
  5. Baris 20 (else): cabang yang dieksekusi setelah longjmp. Variabel rc berisi 42, sesuai argumen longjmp.

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.

c
#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:

text
100 / 5 = 20
84 / 0 gagal: Pembagian dengan nol!
99 / 3 = 33

Struktur 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.

c
#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:

text
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.