Semua contoh sejauh ini menggunakan string yang sudah tertulis di kode sumber. Dalam program nyata, kita sering perlu membaca string dari standard input—baik dari keyboard maupun dari pipe antar proses. Standard library C menyediakan beberapa cara untuk membaca string: scanf dengan format %s, scanf dengan batas lebar %Ns, dan fgets. Sayangnya, scanf("%s", buf) tanpa batas lebar adalah salah satu sumber buffer overflow paling sering dijumpai pada kode pemula: fungsi tersebut terus menulis byte ke buf sampai menemui whitespace, tanpa pernah memeriksa apakah buf sudah penuh. Pada chapter ini kita akan mendemonstrasikan bahaya scanf("%s"), kemudian memperbaikinya dengan scanf("%9s") yang memberi batas lebar, dan akhirnya merekomendasikan fgets sebagai pendekatan paling robust untuk membaca satu baris penuh dari standard input.

Jebakan scanf("%s") #

Bentuk scanf("%s", buf) tanpa batas lebar memang terlihat paling sederhana, tetapi berbahaya. Fungsi ini terus menulis byte ke buf sampai menemui whitespace (spasi, tab, newline), tanpa pernah mengecek apakah buf sudah penuh. Mari kita lihat kode berbahaya yang sebaiknya tidak ditiru.

c
#include <stdio.h>

int main(void) {
    char nama[10];

    /* BAHAYA: scanf("%s") tanpa batas lebar dapat overflow buffer.
       Input > 9 karakter akan menimpa memori di luar nama[]. */
    printf("Masukkan nama: ");
    fflush(stdout);
    int r = scanf("%s", nama);   /* tidak ada batas: berbahaya */
    if (r == 1) {
        printf("Halo, %s\n", nama);
    }
    return 0;
}

Output yang Dihasilkan (input "Budi"):

text
Masukkan nama: Halo, Budi

Pada contoh ini kita memberi input pendek "Budi" (4 karakter) sehingga program berjalan normal tanpa kecelakaan. Akan tetapi, kode di atas tetap berbahaya: bila pengguna memberi input "BudiSangatPanjangSekali" (24 karakter), scanf akan menulis 24 byte plus null terminator ke buffer nama yang hanya berkapasitas 10 byte. Empat belas byte lebih akan menimpa memori di sebelah nama—mungkin menimpa return address di stack frame atau variabel lain. Bila program dikompilasi dengan proteksi stack canary atau FORTIFY_SOURCE, program akan crash dengan pesan stack smashing detected; tanpa proteksi, program mungkin masih berjalan tetapi dengan undefined behavior yang dapat dieksploitasi penyerang.

scanf dengan Batas Lebar #

Cara pertama memperbaiki scanf("%s") adalah dengan menambahkan batas lebar eksplisit dalam format specifier: scanf("%9s", buf) membatasi pembacaan ke maksimal 9 karakter (menyisakan 1 byte untuk null terminator).

c
#include <stdio.h>
#include <string.h>

/* Alternatif aman: scanf dengan batas lebar */
int main(void) {
    char nama[10];

    /* Batas lebar %9s: scanf maksimal baca 9 karakter lalu tambah '\0' */
    printf("Masukkan nama: ");
    fflush(stdout);
    int r = scanf("%9s", nama);
    if (r == 1) {
        printf("Halo, %s (panjang %zu)\n", nama, strlen(nama));
    }

    /* Catatan: scanf %s berhenti di whitespace */
    /* Input "Budi Santoso" hanya akan menangkap "Budi" */
    return 0;
}

Output yang Dihasilkan (input "Budi Santoso"):

text
Masukkan nama: Halo, Budi (panjang 4)

Mari kita telaah baris-baris kode di atas:

  1. Baris 9 (scanf("%9s", nama);): angka 9 di antara % dan s adalah batas lebar. scanf membaca maksimal 9 karakter non-whitespace, lalu menambahkan null terminator. Buffer nama (10 byte) terisi penuh tanpa overflow.
  2. Baris 11 (printf("Halo, %s (panjang %zu)\n", ...)): meskipun input adalah "Budi Santoso" (12 karakter), scanf berhenti di spasi setelah "Budi" karena %s berhenti di whitespace. Hanya 4 karakter yang tersimpan di nama, dan karakter sisanya (" Santoso") tetap berada di stdin buffer.
  3. Konsekuensi: input yang lebih panjang dari 9 karakter akan terpotong tanpa overflow. Namun karakter sisa tetap ada di stdin dan bisa terbaca oleh pemanggilan scanf berikutnya—hal yang sering membingungkan pemula.

fgets: Membaca Satu Baris Penuh #

fgets(buf, size, stream) adalah fungsi yang direkomendasikan untuk membaca string dari stream. Fungsi ini membaca sampai salah satu dari tiga kondisi terpenuhi: (1) size - 1 karakter sudah terbaca, (2) menemui karakter newline '\n', atau (3) end-of-file. Berbeda dari scanf("%s"), fgets tidak berhenti di whitespace—ia membaca seluruh baris termasuk spasi.

c
#include <stdio.h>
#include <string.h>

/* fgets: cara paling aman membaca satu baris penuh */
int main(void) {
    char buf[16];

    printf("Masukkan satu baris: ");
    fflush(stdout);
    if (fgets(buf, sizeof buf, stdin) != NULL) {
        /* fgets menyertakan '\n' bila muat; hapus dengan strcspn */
        buf[strcspn(buf, "\n")] = '\0';
        printf("Tersimpan: [%s]\n", buf);
        printf("Panjang: %zu (maks %zu)\n", strlen(buf), sizeof buf - 1);
    }
    return 0;
}

Output yang Dihasilkan (input "Pemrograman C menyenangkan"):

text
Masukkan satu baris: Tersimpan: [Pemrograman C m]
Panjang: 15 (maks 15)

Mari kita telaah baris-baris kode di atas:

  1. Baris 8 (fgets(buf, sizeof buf, stdin)): argumen kedua adalah ukuran buffer. fgets membaca maksimal sizeof buf - 1 karakter (15 karakter) lalu menambahkan null terminator. Argumen ketiga stdin adalah stream sumber. Fungsi mengembalikan buf bila berhasil, atau NULL bila end-of-file atau terjadi error—karena itu selalu cek != NULL.
  2. Baris 10 (buf[strcspn(buf, "\n")] = '\0';): ini adalah trik kunci. strcspn(buf, "\n") mengembalikan indeks karakter newline pertama di buf, atau panjang string bila tidak ada newline. Kita meng-overwrite posisi tersebut dengan '\0'. Efeknya: bila fgets menyertakan newline (yang terjadi bila input pendek), newline akan terhapus; bila tidak ada newline (input terpotong sebelum newline), tidak ada perubahan. Trik ini lebih elegan daripada cek manual if (n > 0 && buf[n-1] == '\n').
  3. Baris 11-12 (printf("Tersimpan: [%s]\n", buf);): input "Pemrograman C menyenangkan" (27 karakter) terpotong jadi "Pemrograman C m" (15 karakter). Tidak ada overflow, tidak ada crash. fgets menghormati batas buffer.
  4. Baris 13 (printf("Panjang: %zu (maks %zu)\n", strlen(buf), sizeof buf - 1)): panjang tersimpan adalah 15, yang juga adalah sizeof buf - 1. Ini menandakan input terpotong—buffer tidak cukup untuk menampung seluruh baris. Karakter sisanya tetap ada di stdin dan bisa dibaca di pemanggilan fgets berikutnya.

Ringkasan Konsep #

Membaca string dari standard input di C dapat dilakukan dengan tiga cara: scanf("%s", buf) (berbahaya, tidak ada batas), scanf("%Ns", buf) (aman tetapi berhenti di whitespace dan batas lebar harus literal), atau fgets(buf, sizeof buf, stdin) (aman dan robust, membaca satu baris penuh). Rekomendasi untuk kode produksi: gunakan fgets. Setelah pemanggilan fgets, biasakan menghapus newline trailing dengan buf[strcspn(buf, "\n")] = '\0';. Periksa apakah input terpotong dengan membandingkan strlen(buf) dengan sizeof buf - 1; bila sama, karakter sisanya masih ada di stdin dan perlu dibersihkan. Pada chapter berikutnya kita akan membahas strtok—fungsi untuk memecah string yang dibaca dari input menjadi token-token yang lebih kecil berdasarkan delimiter.