Episode ini membahas cara menangani dan menemukan bug: error propagation memakai return code dan errno, diagnostik dengan assert dan logging, sesi debugging dengan GDB dan LLDB yang memakai breakpoint, backtrace, dan watchpoint, serta static analysis tools clang-tidy, cppcheck, dan sanitizers.

Program yang benar hanyalah program yang bug-nya belum ditemukan. Episode 11 membahas dua sisi keandalan: bagaimana program melaporkan error dengan rapi, dan bagaimana programmer menemukan akar masalah dengan alat yang tepat.
C tidak punya exception seperti bahasa modern. Error disebarkan lewat nilai kembalian, variabel errno, dan status keluar. Kalian perlu memilih dan konsisten dengan satu strategi agar aliran error bisa diikuti.
Untuk menemukan bug, C memakai persenjataan berlapis: assert dan logging untuk deteksi awal, GDB dan LLDB untuk inspeksi interaktif, lalu clang-tidy, cppcheck, dan sanitizers untuk menemukan kesalahan yang tidak terlihat mata.
Strategi paling umum: fungsi mengembalikan kode status, di mana 0 berarti sukses dan nilai bukan nol berarti error. Nilai hasil sebenarnya diserahkan lewat pointer parameter:
#include <stdio.h>
int bagi(int a, int b, int *hasil) {
if (b == 0) {
return -1;
}
*hasil = a / b;
return 0;
}
int main(void) {
int hasil;
if (bagi(10, 0, &hasil) != 0) {
fprintf(stderr, "gagal membagi\n");
return 1;
}
printf("%d\n", hasil);
return 0;
}Fungsi bagi(10, 0, &hasil) mengembalikan -1 saat pembagi nol dan menulis hasil lewat pointer saat sukses. Pemanggil memeriksa nilai kembalian sebelum memakai hasil. Pola ini menuntut disiplin: setiap pemanggil wajib memeriksa status.
Untuk error sistem, errno menyimpan kode kegagalan dan perror mencetaknya. Di sisi proses, nilai kembalian main menjadi status keluar program, yang bisa diperiksa shell. Konsistensi: selalu pilih satu strategi per modul dan dokumentasikan kontraknya agar tidak membingungkan pemanggil.
Macro assert dari <assert.h> memeriksa kondisi saat debug. Jika gagal, program berhenti dan mencetak lokasi:
#include <assert.h>
int kuadrat(int x) {
return x * x;
}
int main(void) {
assert(kuadrat(4) == 16);
assert(kuadrat(-3) == 9);
return 0;
}assert(kuadrat(4) == 16) memvalidasi asumsi yang harus selalu benar. Ketika didefinisikan NDEBUG, semua assert dihilangkan dari binary. Gunakan assert untuk kondisi yang memang tidak boleh terjadi, bukan untuk validasi input pengguna.
Logging mencatat apa yang terjadi saat program berjalan. Dengan fprintf(stderr, ...) untuk pesan error dan level log untuk debug, kalian bisa melacak urutan kejadian. Di produksi, log berfungsi sebagai saksi — topik yang akan diperluas menjadi observability di episode 22.
GDB adalah debugger GNU yang bekerja dengan executable yang dikompilasi memakai -g. Mulai sesi dan pasang breakpoint:
gcc -g -Wall program.c -o program
gdb -q ./programDi dalam prompt (gdb), kalian mengetik perintah. break main memasang breakpoint di awal fungsi main, run menjalankan program hingga berhenti, dan next mengeksekusi satu baris tanpa masuk ke fungsi yang dipanggil. Untuk masuk ke dalam fungsi, pakai step.
Saat program berhenti, bt mencetak backtrace — rantai pemanggilan fungsi yang membawa eksekusi ke titik ini. Backtrace menjawab pertanyaan paling penting saat crash: dari mana fungsi ini dipanggil.
watch memasang watchpoint yang berhenti ketika nilai variabel berubah:
(gdb) break main
(gdb) run
(gdb) watch variabel
(gdb) continue
(gdb) bt
(gdb) print variabelwatch variabel di atas memicu berhenti setiap kali nilai berubah, berguna untuk menemukan siapa yang merusak data. print variabel memeriksa isi variabel saat ini. LLDB adalah alternatif modern dengan perintah serupa: breakpoint set, run, dan bt.
Static analysis menemukan bug tanpa menjalankan program. clang-tidy memeriksa konvensi dan masalah potensial, sedangkan cppcheck berfokus pada bug nyata seperti null dereference dan penggunaan memori setelah bebas:
clang-tidy program.c -- -Iinclude
cppcheck --enable=all program.cPerintah cppcheck --enable=all program.c menganalisis file tanpa kompilasi. Jalankan keduanya di setiap perubahan — alat ini menemukan bug yang lolos dari mata manusia dan bahkan dari compiler.
Sanitizers menanamkan pemeriksaan ke dalam binary saat kompilasi. AddressSanitizer mendeteksi out-of-bounds dan use-after-free, UndefinedBehaviorSanitizer mendeteksi perilaku tak terdefinisi:
gcc -g -fsanitize=address,undefined program.c -o program
./programBendera -fsanitize=address,undefined menautkan runtime yang memeriksa memori setiap akses. Ketika ada kesalahan, program berhenti dengan laporan detail lokasi bug. Sanitizers menambah overhead, jadi aktifkan saat pengembangan dan matikan di build produksi.
Tip
Urutan strategi yang disarankan: kompilasi dengan -Wall -Wextra, jalankan clang-tidy dan cppcheck, bangun dengan sanitizers, lalu baru pakai GDB untuk kasus yang tersisa. Setiap lapisan menangkap kelas bug yang berbeda.
Inti yang harus dibawa pulang:
Di episode 12 selanjutnya kita akan membahas networking dan sockets — dasar socket programming TCP dan UDP, API socket seperti socket, bind, listen, accept, connect, send, dan recv, client-server sederhana dengan pertukaran pesan, hingga network byte order, socket address, dan error handling.