Pengguna adalah sumber input yang tidak bisa dipercaya. Mereka mungkin mengetik huruf ketika diminta angka, menekan Ctrl-D di tengah input, atau memasukkan bilangan yang sangat besar sehingga melampaui batas long. Program yang tidak menangani kasus-kasus ini dengan baik akan crash, menghasilkan nilai absurd, atau berputar tanpa henti. Pada chapter ini kita akan mempelajari tiga lapis pertahanan terhadap input tidak valid: memeriksa nilai kembalian scanf untuk mendeteksi kegagalan, mengganti scanf dengan kombinasi fgets+strtol untuk validasi ketat, serta membangun loop retry yang meminta input ulang dengan batasan jumlah percobaan.

Lapis Pertama: Periksa return value scanf #

Cara paling sederhana menangani input tidak valid adalah memeriksa nilai kembalian scanf. Fungsi ini mengembalikan jumlah item yang berhasil dibaca, atau EOF (biasanya -1) jika input berakhir lebih awal sebelum ada karakter yang bisa dibaca. Jika scanf mencoba membaca %d tetapi input adalah huruf, scanf akan mengembalikan 0 dan meninggalkan karakter yang tidak valid di buffer stdin.

c
#include <stdio.h>

int main(void) {
    int umur;

    printf("Masukkan umur Anda: ");
    int hasil = scanf("%d", &umur);

    if (hasil == EOF) {
        printf("Error: input berakhir (EOF).\n");
        return 1;
    } else if (hasil == 0) {
        printf("Error: input bukan angka. Coba lagi.\n");
        return 1;
    } else {
        printf("Umur Anda: %d\n", umur);
    }
    return 0;
}

Output yang Dihasilkan untuk berbagai input:

text
$ echo "25" | ./program
Masukkan umur Anda: Umur Anda: 25

$ echo "bukan_angka" | ./program
Masukkan umur Anda: Error: input bukan angka. Coba lagi.

$ printf "" | ./program
Masukkan umur Anda: Error: input berakhir (EOF).

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (int hasil = scanf("%d", &umur);): nilai kembalian scanf disimpan di hasil. Jika berhasil membaca satu integer, nilai kembalian adalah 1.
  2. Baris 8 (if (hasil == EOF)): jika pengguna menekan Ctrl-D (atau input pipa berakhir) tanpa mengetik apa pun, scanf mengembalikan EOF (konstanta integer negatif, biasanya -1).
  3. Baris 11 (else if (hasil == 0)): jika input dimulai dengan karakter yang bukan digit (atau tanda minus), scanf gagal membaca integer dan mengembalikan 0. Karakter yang tidak valid tetap berada di buffer stdin.
  4. Baris 14 (else): kasus sukses. Nilai kembalian 1 berarti satu item integer berhasil dibaca dan disimpan ke variabel umur.

Lapis Kedua: Validasi Ketat dengan fgets dan strtol #

Pendekatan yang lebih kokoh adalah membaca seluruh baris dengan fgets, lalu memvalidasinya dengan strtol yang melaporkan di mana parsing berhenti. Dengan memeriksa endptr, kita bisa memastikan seluruh string adalah angka valid tanpa karakter sisa seperti 12.5 atau 12abc. Kita juga memeriksa errno untuk mendeteksi overflow.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>

/* Cek apakah string berisi bilangan integer valid */
int parse_int(const char *s, long *out) {
    /* Lewati whitespace awal */
    while (*s != '\0' && isspace((unsigned char)*s)) {
        s++;
    }
    if (*s == '\0') return 0;  /* string kosong */

    errno = 0;
    char *endptr;
    long v = strtol(s, &endptr, 10);

    if (endptr == s) return 0;             /* tidak ada angka */
    if (errno == ERANGE) return 0;         /* overflow */
    while (*endptr != '\0' && isspace((unsigned char)*endptr)) {
        endptr++;
    }
    if (*endptr != '\0') return 0;         /* ada karakter sisa */

    *out = v;
    return 1;
}

int main(void) {
    char input[64];

    printf("Masukkan bilangan bulat: ");
    if (fgets(input, sizeof(input), stdin) == NULL) {
        fprintf(stderr, "Error: input berakhir (EOF).\n");
        return 1;
    }
    input[strcspn(input, "\n")] = '\0';

    long nilai;
    if (parse_int(input, &nilai)) {
        printf("Valid: nilai = %ld\n", nilai);
    } else {
        fprintf(stderr, "Error: \"%s\" bukan bilangan bulat valid.\n", input);
        return 1;
    }
    return 0;
}

Output yang Dihasilkan untuk berbagai input:

text
$ echo "42" | ./program
Masukkan bilangan bulat: Valid: nilai = 42

$ echo "-7" | ./program
Masukkan bilangan bulat: Valid: nilai = -7

$ echo "12.5" | ./program
Masukkan bilangan bulat: Error: "12.5" bukan bilangan bulat valid.

$ echo "abc" | ./program
Masukkan bilangan bulat: Error: "abc" bukan bilangan bulat valid.

$ echo "99999999999999999999" | ./program
Masukkan bilangan bulat: Error: "99999999999999999999" bukan bilangan bulat valid.

Mari kita telaah baris-baris kode di atas:

  1. Baris 11 (int parse_int(const char *s, long *out)): fungsi helper yang mengembalikan 1 jika string s berisi bilangan bulat valid, dan menulis hasilnya ke *out. Jika tidak valid, mengembalikan 0 tanpa memodifikasi *out.
  2. Baris 13-15 (loop while pertama): melewatkan whitespace di awal string agar input seperti " 42" tetap diterima.
  3. Baris 16 (if (*s == '\0') return 0;): jika setelah whitespace string kosong, berarti input tidak mengandung angka sama sekali.
  4. Baris 18 (errno = 0;): wajib direset sebelum memanggil strtol agar kita tidak membaca error lama.
  5. Baris 21 (long v = strtol(s, &endptr, 10);): mengonversi string menjadi long basis 10. endptr akan menunjuk ke karakter pertama yang tidak ikut diparse.
  6. Baris 23 (if (endptr == s) return 0;): jika endptr sama dengan s, tidak ada karakter angka yang berhasil dibaca (misalnya input "abc").
  7. Baris 24 (if (errno == ERANGE) return 0;): jika input melebihi batas long, strtol mengatur errno = ERANGE. Input seperti 99999999999999999999 akan tertangkap di sini.
  8. Baris 25-27 (loop while kedua): melewatkan whitespace di akhir string agar input seperti "42 " tetap diterima.
  9. Baris 28 (if (*endptr != '\0') return 0;): jika ada karakter selain whitespace di akhir (misalnya "12.5" atau "12abc"), input tidak valid.
  10. Baris 31 (return 1;): sukses. Nilai v ditulis ke *out dan fungsi mengembalikan 1.

Lapis Ketiga: Loop Retry dengan Batas Percobaan #

Setelah kita punya fungsi validasi, kita bisa membangun loop yang meminta input ulang sampai valid atau sampai pengguna kehabisan percobaan. Pola ini umum di program CLI yang tidak ingin crash pada kesalahan pertama, tetapi juga ingin mencegah loop tak terhingga akibat pengguna yang terus memasukkan input salah.

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

int parse_int(const char *s, long *out) {
    while (*s != '\0' && isspace((unsigned char)*s)) s++;
    if (*s == '\0') return 0;

    errno = 0;
    char *endptr;
    long v = strtol(s, &endptr, 10);

    if (endptr == s) return 0;
    if (errno == ERANGE) return 0;
    while (*endptr != '\0' && isspace((unsigned char)*endptr)) endptr++;
    if (*endptr != '\0') return 0;

    *out = v;
    return 1;
}

int main(void) {
    char input[64];
    long nilai;
    int percobaan = 0;
    int maks_percobaan = 3;

    while (percobaan < maks_percobaan) {
        printf("Masukkan umur (0-150): ");
        if (fgets(input, sizeof(input), stdin) == NULL) {
            fprintf(stderr, "\nError: input berakhir (EOF).\n");
            return 1;
        }
        input[strcspn(input, "\n")] = '\0';

        if (!parse_int(input, &nilai)) {
            percobaan++;
            fprintf(stderr, "  Bukan angka. Sisa percobaan: %d\n",
                    maks_percobaan - percobaan);
            continue;
        }

        if (nilai < 0 || nilai > 150) {
            percobaan++;
            fprintf(stderr, "  Umur di luar rentang 0-150. Sisa percobaan: %d\n",
                    maks_percobaan - percobaan);
            continue;
        }

        printf("Terima kasih! Umur Anda: %ld\n", nilai);
        return 0;
    }

    fprintf(stderr, "Terlalu banyak percobaan. Program berhenti.\n");
    return 1;
}

Output yang Dihasilkan (input: abc, 200, 25):

text
Masukkan umur (0-150): Masukkan umur (0-150): Masukkan umur (0-150): Terima kasih! Umur Anda: 25

Output yang Dihasilkan (input: abc, 200, -5):

text
Masukkan umur (0-150): Masukkan umur (0-150): Masukkan umur (0-150): Terlalu banyak percobaan. Program berhenti.

Dengan stderr yang menampilkan:

text
  Bukan angka. Sisa percobaan: 2
  Umur di luar rentang 0-150. Sisa percobaan: 1
  Umur di luar rentang 0-150. Sisa percobaan: 0

Mari kita telaah baris-baris kode di atas:

  1. Baris 26 (int maks_percobaan = 3;): konstanta batas percobaan. Pengguna hanya boleh gagal 3 kali sebelum program menyerah.
  2. Baris 28 (while (percobaan < maks_percobaan)): loop terus berjalan selama jumlah percobaan belum mencapai batas.
  3. Baris 30 (if (fgets(input, sizeof(input), stdin) == NULL)): jika pengguna menekan Ctrl-D di tengah input, fgets mengembalikan NULL. Kita laporkan error dan keluar.
  4. Baris 35 (if (!parse_int(input, &nilai))): jika parsing gagal, tambah penghitung percobaan dan laporkan ke stderr. Pernyataan continue melompat ke iterasi berikutnya.
  5. Baris 40 (if (nilai < 0 || nilai > 150)): validasi tambahan — angka valid secara sintaks tetapi di luar rentang yang masuk akal. Input 200 atau -5 akan tertangkap di sini.
  6. Baris 45 (return 0;): jika semua validasi lolos, cetak konfirmasi sukses dan keluar dengan exit code 0.
  7. Baris 49 (fprintf(stderr, "Terlalu banyak percobaan...")): jika loop keluar karena kehabisan percobaan, laporkan ke stderr dan keluar dengan exit code 1.