Undefined behavior (UB) adalah konstruksi kode C yang tidak memiliki perilaku terdefinisi oleh standar. Compiler bebas mengasumsikan UB tidak terjadi—dan dapat mengoptimasi kode berdasarkan asumsi tersebut. Akibatnya, kode yang “berjalan baik” hari ini bisa gagal misterius setelah perubahan flag optimasi atau upgrade compiler. Bab ini menyusun daftar UB paling berbahaya beserta cara mendeteksi dan mencegahnya.

Apa itu UB? #

Saat compiler mengoptimasi, compiler boleh mengasumsikan program tidak mengandung UB. Asumsi ini memungkinkan transformasi yang agresif—misalnya, loop for (int i = 0; i <= N; i++) dianggap tidak akan overflow, sehingga compiler dapat menghapus pengecekan batas. Konsekuensinya: kode dengan UB dapat berjalan “normal” pada -O0 tetapi rusak pada -O2.

Tabel UB Utama #

#KategoriContohBahaya
1Signed integer overflowINT_MAX + 1Wrap-around biasa, tetapi compiler dapat menghapus cek
2Null pointer dereference*p = 10; dengan p = NULLSegfault atau perilaku tak terduga
3Use-after-freefree(p); p->x = 1;Korupsi heap, crash asinkron
4Double-freefree(p); free(p);Korupsi free list allocator
5Buffer overflowarr[5] saat arr[3]Korupsi stack/heap, eksekusi kode
6Uninitialized readint x; printf("%d", x);Nilai tidak terduga, info leak
7Invalid shiftx << 32 pada int 32-bitHasil tidak terdefinisi
8Strict aliasing violationfloat *f = (float*)&int_var;Compiler reorder load/store
9Modifikasi string literalchar *s = "halo"; s[0] = 'H';Segfault di platform read-only
10Modifikasi objek const(int*)&const_var = 20;Compiler asumsikan tidak berubah
11Sequence point violationprintf("%d %d", i++, i++);Urutan evaluasi tidak terdefinisi
12Out-of-bound pointer arithmetic&arr[-1]UB kecuali satu past end
13Divide by zeroint r = 5 / 0;Trap signal (SIGFPE)
14Returning pointer to localreturn &local_var;Stack sudah di-unwind
15Modifying literal in strtokstrtok("a,b", ",");String literal read-only
16Negative size_t to mallocmalloc((size_t)-1);Bisa alokasi 0 atau fail
17Comparison memcmp antar struct paddingpadding berisi nilai sampahHasil perbandingan tidak deterministik
18Function call tanpa prototipeint r = strlen(s); tanpa #include <string.h>Tipe parameter assumed int

Demonstrasi UB dan Padaban Aman #

Program berikut mendemonstrasikan beberapa UB yang dapat dijalankan dengan aman (outputnya biasanya normal, tetapi tidak dijamin) dan padanannya yang well-defined.

c
/* File: ub1.c - Demonstrasi UB dan padanan amannya */
#include <stdio.h>
#include <limits.h>
#include <stdint.h>

int main(void) {
    /* Kasus 1: Signed integer overflow (UB) */
    int a = INT_MAX;
    printf("1. INT_MAX + 1 = %d   (UB, biasa wrap ke negatif)\n", a + 1);

    /* Kasus 2: Unsigned overflow (well-defined wrap) */
    unsigned u = 0;
    printf("2. unsigned 0 - 1 = %u   (well-defined)\n", u - 1);

    /* Kasus 3: char* aliasing (selalu legal) */
    int n = 0x41414141;
    unsigned char *cp = (unsigned char*)&n;
    printf("3. byte ke-0 n = %d   (char* aliasing OK)\n", cp[0]);

    /* Kasus 4: Union type punning (legal di C99+) */
    union { uint32_t i; float f; } un;
    un.i = 0x40490FDBu;
    printf("4. union punning 0x40490FDB -> float = %f   (legal)\n", un.f);

    return 0;
}

Compile dan jalankan dalam tiga mode untuk membandingkan:

bash
# Tanpa sanitasi
gcc -std=c11 -Wall -Wextra -O0 -o ub1 ub1.c && ./ub1

# Dengan optimasi (UB bisa terlihat berbeda)
gcc -std=c11 -Wall -Wextra -O2 -o ub1_opt ub1.c && ./ub1_opt

# Dengan UBSan untuk deteksi UB
gcc -std=c11 -Wall -Wextra -fsanitize=undefined -o ub1_ubsan ub1.c && ./ub1_ubsan

Output yang Dihasilkan (-O0 dan -O2 identik di sini, tetapi tidak dijamin):

text
1. INT_MAX + 1 = -2147483648   (UB, biasa wrap ke negatif)
2. unsigned 0 - 1 = 4294967295   (well-defined)
3. byte ke-0 n = 65   (char* aliasing OK)
4. union punning 0x40490FDB -> float = 3.141593   (legal)

Output dengan UBSan (stderr):

text
ub1.c:9:5: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'

Mari kita telaah baris-baris kode di atas:

  1. Baris 9 (a + 1 dengan a = INT_MAX): Ini adalah UB—signed integer overflow. Output -2147483648 adalah perilaku wrap-around yang umum pada arsitektur two’s complement, tetapi standar tidak menjaminnya. Compiler yang melihat if (a + 1 > a) dapat mengoptimasi menjadi true selalu—yang akan membuat pengecekan overflow tidak berfungsi.
  2. Baris 13 (u - 1 dengan u = 0): Untuk unsigned, overflow didefinisikan sebagai wrap-around modulo 2^N. Hasil 0 - 1 selalu UINT_MAX—tidak ada UB.
  3. Baris 17 ((unsigned char*)&n): Aturan strict aliasing melarang akses objek via pointer tipe yang tidak kompatibel. Pengecualian: char*, signed char*, dan unsigned char* boleh alias objek tipe apapun. Inilah cara aman untuk inspeksi byte per byte.
  4. Baris 22 (union { uint32_t i; float f; }): Type punning via union adalah UB di C++ tetapi legal di C99+. Ketika satu field di-read setelah field lain ditulis, compiler membaca representasi bit yang sama. Cara ini lebih aman daripada *(float*)&int_var yang melanggar strict aliasing.

Deteksi dengan UBSan #

UBSan adalah tool bawaan GCC/Clang yang menginstrumentasi binary untuk mendeteksi UB saat runtime. Aktifkan dengan -fsanitize=undefined (kombinasi banyak check) atau check spesifik:

Flag UBSanDeteksi
-fsanitize=signed-integer-overflowSigned overflow
-fsanitize=unsigned-integer-overflowUnsigned overflow (tidak UB, tapi sering bug)
-fsanitize=shiftShift terlalu besar atau negatif
-fsanitize=nullNull pointer dereference
-fsanitize=alignmentAkses unaligned
-fsanitize=boundsAkses array out-of-bound (variable-length array)
-fsanitize=vla-boundVLA dengan ukuran non-positif
-fsanitize=object-sizeAkses ke objek lebih kecil dari yang diperkirakan
-fsanitize=pointer-overflowPointer arithmetic overflow
-fsanitize=undefinedKombinasi semua di atas

Strategi Pencegahan UB #

StrategiImplementasi
Gunakan tipe unsigned jika nilai tidak negatifsize_t untuk ukuran, uint32_t untuk ID
Pakai INT64_MAX check sebelum tambahif (a > INT64_MAX - b) ...
Inisialisasi semua variabel saat deklarasiint x = 0; bukan int x;
Hindari realloc(p, 0) untuk “free”Pakai free(p); p = NULL;
Set pointer ke NULL setelah freefree(p); p = NULL;
Hindari strtok untuk string konstanSalin dulu ke buffer mutable
Pakai memcpy untuk type punning, bukan cast pointermemcpy(&f, &i, sizeof f);
Verifikasi malloc tidak return NULLif (!p) return -1;
Aktifkan -fanalyzer saat testingGCC statik analyzer deteksi use-after-free
Pakai ASan + UBSan untuk testing-fsanitize=address,undefined

Daftar Bacaan #

  • ISO/IEC 9899:2011 Annex J.2 — daftar lengkap UB
  • cppreference.com/w/c/language/behavior — kategori UB
  • clang.llvm.org/docs/UndefinedBehaviorSanitizer.html — dokumentasi UBSan
  • blog.regehr.org — John Regehr menulis banyak tentang UB C