Bug adalah bagian tak terhindarkan dari pemrograman, termasuk di C. Bahkan programmer berpengalaman pun menulis kode yang gagal compile atau menghasilkan output salah pada percobaan pertama. Yang membedakan programmer pemula dari yang berpengalaman adalah kemampuan membaca pesan kesalahan compiler dan menjelaskan isi variabel saat program berjalan. Pada Chapter ini kita akan mempraktikkan tiga teknik debugging dasar: printf debugging, assert, dan membaca pesan kesalahan compiler.
Membaca Pesan Kesalahan Compiler #
Saat compiler menemukan kesalahan sintaks, ia mencetak pesan yang sering kali terlihat menakutkan bagi pemula. Padahal, pesan tersebut selalu menyertakan tiga informasi penting: nama file, nomor baris, dan deskripsi kesalahan. Mari kita lihat contoh kode yang sengaja mengandung kesalahan:
#include <stdio.h>
int main(void) {
int nilai = 10
printf("Nilai = %d\n", nilai)
return 0;
}Kompilasi kode di atas akan gagal. Mari kita lihat pesan yang dihasilkan:
gcc -std=c11 -Wall -Wextra -o error_demo error_demo.cOutput yang Dihasilkan:
error_demo.c: In function 'main':
error_demo.c:5:5: error: expected ',' or ';' before 'printf'
5 | printf("Nilai = %d\n", nilai)
| ^~~~~~
error_demo.c:4:9: warning: unused variable 'nilai' [-Wunused-variable]
4 | int nilai = 10
| ^~~~~Mari kita telaah baris-baris pesan di atas:
- Baris 1 (
error_demo.c: In function 'main':): compiler menyatakan sedang memeriksa fungsimaindi fileerror_demo.c. Baris ini hanya penanda konteks. - Baris 2 (
error_demo.c:5:5: error: ...): formatfile:baris:kolom: error: pesan. Kesalahan berada di baris 5, kolom 5. Compiler mengharapkan tanda koma atau titik koma sebelum kataprintf— yang sebenarnya berarti titik koma hilang di akhir baris 4. - Baris 3-4: cuplikan kode sumber dengan penanda
^~~~~~menunjuk lokasi tepatnya kesalahan ditemukan. - Baris 5 (
error_demo.c:4:9: warning: ...): peringatan tambahan bahwa variabelnilaitidak dipakai. Karena compiler berhenti setelah baris 4 (tidak ada titik koma), variabelnilaitidak pernah terbaca; compiler menandai ini sebagai unused variable.
- Pesan kesalahan compiler sering kali menunjuk baris setelah lokasi kesalahan yang sebenarnya. Bila compiler mengeluh “expected ‘;’ before X” di baris N, periksa baris N-1: kemungkinan besar titik koma hilang di akhir baris N-1.
- Peringatan (
warning) tidak menghentikan kompilasi, tetapi kesalahan (error) menghentikannya. Selalu baca pesan secara berurutan dari atas ke bawah dan perbaiki kesalahan pertama terlebih dahulu; sering kali kesalahan berikutnya adalah efek domino dari kesalahan pertama.
Teknik printf Debugging #
Teknik debugging paling universal di C adalah menambahkan pemanggilan printf di sepanjang jalur eksekusi untuk memeriksa nilai variabel dan urutan pemanggilan fungsi. Teknik ini tidak memerlukan tool khusus dan bekerja di semua platform.
#include <stdio.h>
int faktorial(int n) {
int hasil = 1;
printf("[DEBUG] memanggil faktorial(%d)\n", n);
for (int i = 1; i <= n; i++) {
hasil *= i;
printf("[DEBUG] i=%d, hasil=%d\n", i, hasil);
}
return hasil;
}
int main(void) {
int f = faktorial(5);
printf("5! = %d\n", f);
return 0;
}Output yang Dihasilkan:
[DEBUG] memanggil faktorial(5)
[DEBUG] i=1, hasil=1
[DEBUG] i=2, hasil=2
[DEBUG] i=3, hasil=6
[DEBUG] i=4, hasil=24
[DEBUG] i=5, hasil=120
5! = 120Mari kita telaah baris-baris kode di atas:
- Baris 5 (
printf("[DEBUG] memanggil faktorial(%d)\n", n);): mencetak nilai parameternsaat fungsi dipanggil; prefiks[DEBUG]memudahkan kita memfilter baris ini dari output normal. - Baris 7-9 (
forloop denganprintfdi dalamnya): setiap iterasi mencetak nilaiidanhasilakumulasi; kita bisa melihat bagaimanahasiltumbuh dari1menjadi120. - Baris 12 (
int f = faktorial(5);): pemanggilan fungsi; output debug muncul sebelum barisprintf("5! = %d\n", f)karena pemanggilanfaktorialdieksekusi lebih dulu.
Untuk proyek yang lebih besar, ganti printf langsung dengan makro #define DEBUG_PRINT(fmt, ...) printf("[DEBUG] " fmt "\n", __VA_ARGS__) agar seluruh debug bisa dimatikan dengan satu baris #undef DEBUG_PRINT tanpa harus menghapus setiap pemanggilan satu per satu.
Memeriksa dengan assert #
Macro assert dari header <assert.h> menghentikan program bila kondisi yang diberikan bernilai 0 (false). assert sangat berguna untuk memeriksa asumsi yang seharusnya selalu benar jika kode ditulis dengan benar — misalnya, indeks array dalam batas, atau parameter fungsi tidak negatif.
#include <stdio.h>
#include <assert.h>
int main(void) {
int nilai = 10;
assert(nilai > 0);
printf("Assert lolos, nilai = %d\n", nilai);
return 0;
}Output yang Dihasilkan:
Assert lolos, nilai = 10Bila kondisi assert tidak terpenuhi, program dihentikan dengan pesan ke standard error:
#include <stdio.h>
#include <assert.h>
int main(void) {
int nilai = -5;
assert(nilai > 0);
printf("Tidak akan dicetak.\n");
return 0;
}Output yang Dihasilkan:
test_ch14_assert2: /tmp/test_ch14_assert2.c:6: main: Assertion `nilai > 0' failed.Setelah pesan di atas muncul, program berakhir dengan exit code 134 (sinyal SIGABRT). Pesan assertion mencakup nama executable, nama file sumber, nomor baris, nama fungsi, dan ekspresi yang gagal — informasi ini sangat berharga untuk memperbaiki bug.
- Macro
assertdapat dimatikan dengan mendefinisikan makroNDEBUGsebelum menyertakan<assert.h>. Konfigurasi ini biasanya dipakai pada build produksi agar assertion check tidak memperlambat eksekusi. - Gunakan
assertuntuk kondisi yang menandakan bug di kode kita sendiri. Jangan pakaiassertuntuk validasi input dari pengguna — gunakanifeksplisit dan pesan kesalahan yang ramah untuk kasus tersebut.
Memperhatikan Peringatan Compiler #
Compiler GCC dengan flag -Wall -Wextra aktif mengeluarkan peringatan untuk kategori bug umum. Salah satu peringatan yang paling sering muncul adalah unused variable:
#include <stdio.h>
int main(void) {
int nilai = 10;
int tidak_dipakai = 99;
printf("Nilai = %d\n", nilai);
return 0;
}Kompilasi dengan flag ketat:
gcc -std=c11 -Wall -Wextra -o warn_demo warn_demo.cOutput yang Dihasilkan:
warn_demo.c: In function 'main':
warn_demo.c:5:9: warning: unused variable 'tidak_dipakai' [-Wunused-variable]
5 | int tidak_dipakai = 99;
| ^~~~~~~~~~~~~Meskipun program masih berhasil di-compile dan jalan, peringatan ini menandakan ada variabel yang dibuat tetapi tidak pernah dipakai. Variabel tidak terpakai biasanya merupakan sisa refactoring yang lupa dibersihkan, dan bisa membingungkan pembaca kode. Hapus variabel tersebut untuk membuat kode lebih bersih:
#include <stdio.h>
int main(void) {
int nilai = 10;
printf("Nilai = %d\n", nilai);
return 0;
}Output yang Dihasilkan:
Nilai = 10Strategi Debugging Bertahap #
Saat menemui bug, ikuti urutan berikut agar proses debugging lebih efisien:
- Baca pesan kesalahan pertama dari compiler. Banyak kesalahan berikutnya sering kali adalah efek domino; perbaiki kesalahan pertama lalu kompilasi ulang.
- Tambahkan
printfdi sekitar lokasi yang dicurigai. Cetak nilai variabel sebelum dan sesudah operasi yang mencurigakan. - Kecilkan ruang bug. Bila bug muncul pada fungsi besar, coba komentari separuh kode dan lihat apakah bug masih muncul. Lakukan ini hingga bug terisolasi di blok kode yang kecil.
- Gunakan
assertuntuk asumsi. Bila kita yakin variabelindexselalu kurang dari panjang array, tambahkanassert(index < panjang_array);. Bila asumsi ini salah, program berhenti tepat di lokasi yang melanggar. - Untuk bug lebih kompleks, gunakan debugger interaktif seperti
gdbataulldb. Debugger memungkinkan kita menjalankan program baris per baris, memeriksa nilai variabel, dan memodifikasi memori secara langsung. Bab Tool Debugging Lanjutan (di Section selanjutnya) akan membahasgdblebih dalam.
- Jangan pernah mengabaikan peringatan compiler. Peringatan yang tampak sepele hari ini bisa menjadi bug serius esok hari. Sikap yang disarankan: tambahkan flag
-Werroragar semua peringatan diperlakukan sebagai kesalahan, sehingga program tidak akan di-compile bila masih ada peringatan. - Untuk pengembangan C modern, pertimbangkan juga sanitizer
-fsanitize=address,undefinedyang menangkap bug memori (buffer overflow, use-after-free) dan perilaku tak terdefinisi pada saat program berjalan.