Menu

Error Umum di C: Kesalahan Pemula dan Cara Memperbaikinya

Kesalahan yang pernah dibuat setiap programmer C setidaknya sekali: lupa titik koma, = alih-alih ==, implicit declaration, scanf tanpa &, membandingkan string dengan ==, pembagian integer, variabel yang belum diinisialisasi, dan off-by-one. Masing-masing dijelaskan lewat gejala, penyebab, dan solusinya.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Setiap programmer C menemui daftar kesalahan pendek yang sama, biasanya di minggu pertama dan sesekali sepuluh tahun kemudian. Daftar ini layak dicatat karena sebagian besar kesalahan itu menghasilkan pesan error yang tidak menjelaskannya, atau lebih buruk lagi, tidak menghasilkan pesan sama sekali.

Setiap entri di bawah berisi gejala, penyebab di baliknya, dan solusinya.

Titik Koma yang Hilang dan Efek Berantainya

Gejala: deretan error, semuanya dilaporkan di baris yang terlihat benar.

program.c:6:5: error: expected ';' before 'printf'
program.c:7:5: error: expected declaration specifiers before 'return'

Penyebab: C mengakhiri pernyataan dengan ;. Kalau satu terlewat, compiler menempelkan baris berikutnya ke baris saat ini, lalu melaporkan kebingungan di titik di mana gabungan teks itu tidak lagi masuk akal, biasanya di baris berikutnya.

int main(void) {
    int x = 5      /* missing semicolon */
    printf("%d\n", x);
    return 0;
}

Solusi: saat sekumpulan error muncul, perbaiki hanya yang pertama lalu kompilasi ulang. Semua error setelahnya mungkin hanya akibatnya. Dan selalu periksa baris sebelum baris yang disebut compiler.

Efek berantai yang sama muncul dari kurung kurawal yang tidak ditutup atau komentar /* yang tidak diakhiri, di mana error-nya bisa muncul puluhan baris jauhnya.

Satu catatan tambahan: tidak ada titik koma setelah header if, for, while, atau setelah kurung kurawal penutup definisi fungsi. if (x > 0); bisa dikompilasi dan tidak melakukan apa-apa: ; itu adalah seluruh body-nya.

Assignment, Bukan Perbandingan

Gejala: kondisi yang selalu bernilai true, atau variabel yang berubah secara misterius.

int x = 5;
if (x = 10) {              /* assigns 10, then tests 10 -> true */
    printf("x is ten\n");  /* always prints; x is now 10 */
}

Penyebab: = meng-assign, == membandingkan. Nilai sebuah assignment adalah nilai yang di-assign, jadi if (x = 10) menguji 10, yang bukan nol, sehingga bernilai true. Compiler menerimanya karena sesekali memang itu yang dimaksud orang.

Solusi: gunakan == di setiap kondisi, dan nyalakan -Wall supaya compiler memberi peringatan:

warning: suggest parentheses around assignment used as truth value

Kalau kamu memang bermaksud melakukan assignment di dalam kondisi, yang umum di while ((c = getchar()) != EOF), tanda kurung ekstra menyatakan maksud itu dan membungkam peringatannya.

Implicit Declaration pada Fungsi

Gejala:

warning: implicit declaration of function 'printf'
warning: implicit declaration of function 'malloc'

...kadang diikuti hasil aneh saat program berjalan atau error saat linking.

Penyebab: compiler menemukan pemanggilan fungsi yang tidak punya deklarasi. Di dialek sebelum C99, compiler menganggap fungsi itu mengembalikan int lalu jalan terus; di sistem 64-bit, anggapan itu memotong pointer yang dikembalikan menjadi 32 bit. Begitulah <stdlib.h> yang terlupa membuat malloc berujung crash.

Solusi: include header yang tepat.

FungsiHeader
printf, scanf, fopen<stdio.h>
malloc, free, atoi, rand, exit<stdlib.h>
strlen, strcpy, strcmp<string.h>
sqrt, pow, fabs<math.h>
isdigit, toupper<ctype.h>
time<time.h>

Untuk fungsimu sendiri, peringatan yang sama berarti kamu memanggilnya sebelum mendefinisikannya. Taruh prototipe di atas main:

scanf Tanpa &

Gejala: program crash saat menerima input, atau tidak membaca apa pun dan variabelnya tidak berubah.

int age;
scanf("%d", age);     /* missing & : passes the value, not the address */

Penyebab: scanf menulis ke dalam variabelmu, jadi ia butuh alamat variabel itu. Memberikan age berarti menyerahkan nilai sampah apa pun yang ada di dalamnya, dan scanf menganggapnya sebagai tempat untuk menulis, yang biasanya berujung segfault.

Solusi: & sebelum variabel, untuk semua tipe kecuali array, yang sudah merupakan alamat:

Dua jebakan di keluarga yang sama: gunakan %lf untuk double di scanf (%f di sana berarti float, dan menulis byte sebanyak float ke dalam double membuat nilainya salah), dan selalu batasi %s dengan lebar, misalnya %49s untuk buffer 50 byte, atau kata yang panjang akan meluap.

Lebih baik lagi: baca satu baris penuh dengan fgets lalu parse, yang tidak bisa meluap dan tidak meninggalkan sisa input di buffer. Penjelasan lebih lengkap tentang keduanya ada di scanf.

Membandingkan String dengan ==

Gejala: dua string yang jelas sama dianggap berbeda.

char a[] = "hello";
char b[] = "hello";

if (a == b) {              /* comparing two addresses: false */
    printf("same\n");
}

Penyebab: string C bukan sebuah nilai, melainkan pointer ke karakter pertama. == membandingkan pointer-nya. Dua array yang berisi teks yang sama berada di alamat yang berbeda, jadi hasilnya false. (Yang membingungkan, membandingkan dua literal yang sama kadang menghasilkan true, karena compiler mungkin hanya menyimpan satu salinan, sehingga bug ini muncul kadang-kadang saja.)

Solusi: strcmp, dan ingat bahwa ia mengembalikan 0 kalau sama:

Cara membaca yang terbalik juga sering menjebak: if (strcmp(a, b)) bernilai true kalau string-nya berbeda, karena hasil bukan nol berarti "tidak sama". Selalu tulis == 0 secara eksplisit.

Pembagian Integer

Gejala: rata-rata bernilai 0, persentase yang selalu 0 atau 100, rasio yang kehilangan pecahannya.

int correct = 7, total = 10;
double score = correct / total;      /* 0.0, not 0.7 */

Penyebab: kedua operand bertipe int, jadi C melakukan pembagian integer dan memotong hasilnya sebelum di-assign ke double. 7 / 10 adalah 0; mengonversi 0 ke double menghasilkan 0.0.

Solusi: jadikan salah satu operand floating point sebelum pembagian:

Melakukan cast pada satu operand otomatis menaikkan tipe operand lainnya. Melakukan cast pada hasilnya sudah terlambat, karena pemotongan sudah terjadi. Lihat type casting.

Jebakan yang sama bersembunyi di ekspresi seperti (a + b) / 2 untuk titik tengah dan 1 / 2 * x, yang selalu 0 berapa pun nilai x.

return yang Hilang atau Salah

Gejala: sebuah fungsi mengembalikan angka salah yang terlihat masuk akal, berbeda di setiap run atau setiap build.

int add(int a, int b) {
    int sum = a + b;
    /* no return statement */
}

Penyebab: mencapai akhir fungsi non-void tanpa return memberikan nilai yang tidak ditentukan, dalam praktiknya apa pun yang kebetulan ada di register kembalian. Itu undefined behavior kalau pemanggil memakainya.

Bentuk yang lebih licik melakukan return di sebagian jalur saja:

int classify(int n) {
    if (n > 0) return 1;
    if (n < 0) return -1;
    /* n == 0 falls off the end */
}

Solusi: lakukan return di setiap jalur, dan kompilasi dengan -Wall: pesan GCC "control reaches end of non-void function" menangkap kedua versi.

Variabel yang Belum Diinisialisasi

Gejala: output berisi nilai sampah, atau hasil yang berubah antar-run dan antar-level optimasi.

int total;                       /* contains whatever was on the stack */
for (int i = 1; i <= 5; i++) {
    total += i;                  /* adding to garbage */
}
printf("%d\n", total);           /* some enormous number */

Penyebab: variabel lokal tidak diisi nol. Variabel global atau static otomatis bernilai nol; variabel lokal dimulai dengan byte apa pun yang sudah ada di alamat stack itu.

Solusi: inisialisasi di tempat deklarasi. Tidak ada biayanya dan seluruh jenis bug ini hilang:

-Wall -Wextra memperingatkan banyak kasus seperti ini ("may be used uninitialized"), dan -fsanitize=memory atau valgrind menangkap sisanya. Yang ini sangat berbahaya karena pointer yang belum diinisialisasi langsung berujung segmentation fault.

Off-by-One

Gejala: item terakhir terlewat, atau satu elemen kelebihan tersentuh dan program berperilaku aneh belakangan.

int arr[5];
for (int i = 0; i <= 5; i++) {   /* touches arr[5], which does not exist */
    arr[i] = i;
}

Penyebab: array berisi n elemen punya indeks 0 sampai n - 1. <= menjalankan satu putaran ekstra.

Solusi: pola i < n, dan hitung n dari array alih-alih menulis angkanya dua kali:

Versi string-nya, yaitu lupa menyediakan tempat untuk '\0', adalah kesalahan yang sama dalam wujud berbeda, dan char word[5] yang diisi "hello" adalah buffer overrun.

Dua Kesalahan Kecil yang Perlu Diketahui

Titik koma setelah header loop. for (int i = 0; i < 10; i++); yang diikuti blok berkurung kurawal menjalankan loop sepuluh kali tanpa melakukan apa-apa, lalu menjalankan blok itu satu kali. Kodenya bisa dikompilasi tanpa masalah.

sizeof pada pointer. Di dalam fungsi, parameter array adalah pointer, jadi sizeof(arr) adalah ukuran pointer (8 byte), bukan ukuran array. Kirim panjangnya sebagai argumen terpisah:

Kebiasaan yang Mencegah Sebagian Besar Masalah Ini

Kompilasi dengan peringatan menyala, sejak program pertama:

gcc -Wall -Wextra -g program.c -o program

-Wall -Wextra menangkap assignment di dalam kondisi, return yang hilang, pembacaan variabel yang belum diinisialisasi, variabel yang tidak dipakai, dan specifier printf yang tidak cocok dengan argumennya. Menambahkan -fsanitize=address,undefined selama pengembangan menangkap hampir semua sisanya tepat saat terjadi.

Anggap setiap peringatan sebagai error yang belum kamu alami. Program C yang dikompilasi tanpa peringatan belum tentu benar, tetapi hampir setiap program C yang crash sebelumnya sudah memperingatkan sesuatu.

Pertanyaan yang sering diajukan

Apa arti 'implicit declaration of function' di C?

Compiler menemukan pemanggilan fungsi yang belum pernah ia lihat deklarasinya. Hampir selalu penyebabnya adalah #include yang terlupa: printf butuh <stdio.h>, malloc butuh <stdlib.h>, strlen butuh <string.h>. Bisa juga artinya kamu memanggil fungsimu sendiri sebelum mendefinisikannya, yang bisa diperbaiki dengan prototipe di atas main.

Kenapa program C saya melaporkan error di baris yang terlihat benar?

Biasanya karena kesalahan sebenarnya ada di baris sebelumnya. Titik koma yang hilang, kurung kurawal yang tidak ditutup, atau komentar yang tidak diakhiri membuat compiler membaca dua barismu sebagai satu, sehingga ia melaporkan kebingungannya di titik di mana kode itu akhirnya tidak bisa di-parse. Selalu periksa dulu baris di atas baris yang dilaporkan.

Kenapa string tidak bisa dibandingkan dengan == di C?

Karena string C adalah char *, yaitu pointer, jadi == membandingkan dua alamat, bukan karakter yang ditunjuknya. Dua string yang sama persis tetapi disimpan di tempat berbeda menghasilkan false. Gunakan strcmp(a, b) == 0 dari <string.h>, yang mengembalikan 0 kalau isinya sama.

Kenapa 5 / 2 menghasilkan 2 di C?

Karena kedua operand adalah integer, sehingga C melakukan pembagian integer dan membuang pecahannya. Jadikan salah satu sisi bernilai floating-point untuk mendapatkan 2.5: 5.0 / 2, atau (double)a / b kalau memakai variabel. Melakukan cast pada hasilnya sudah terlambat: (double)(5 / 2) adalah 2.0.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai