scanf memang praktis untuk membaca token-token pendek, tetapi fungsi tersebut berhenti pada whitespace pertama dan sering meninggalkan karakter tersisa di buffer stdin yang membingungkan panggilan input berikutnya. Untuk membaca kalimat penuh atau nama lengkap dengan spasi, fungsi fgets adalah pilihan yang lebih aman dan lebih prediktif. Pada chapter ini kita akan mempelajari cara kerja fgets, bagaimana fungsi ini mencegah buffer overflow dengan membatasi pembacaan sesuai ukuran array, serta bagaimana kita menghapus karakter newline \n yang sering ikut terbawa di akhir string hasil bacaan.

Cara Kerja fgets #

Fungsi fgets membaca satu baris dari stream yang diberikan (umumnya stdin) dan menyimpannya ke dalam array char yang sudah disiapkan. Fungsi ini menerima tiga argumen: pointer ke buffer tujuan, ukuran maksimum buffer, dan stream sumber. fgets akan berhenti membaca ketika bertemu newline, ketika buffer hampir penuh, atau ketika mencapai end-of-file. Karakter newline yang ditangkap akan ikut disimpan di dalam buffer sebelum null terminator.

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

int main(void) {
    char nama[32];
    printf("Masukkan nama lengkap: ");
    if (fgets(nama, sizeof(nama), stdin) != NULL) {
        /* Hapus newline di akhir jika ada */
        nama[strcspn(nama, "\n")] = '\0';
        printf("Halo, %s!\n", nama);
    }
    return 0;
}

Output yang Dihasilkan (input: Budi Santoso):

text
Masukkan nama lengkap: Halo, Budi Santoso!

Mari kita telaah baris-baris kode di atas:

  1. Baris 2 (#include <string.h>): header yang menyediakan fungsi strcspn untuk mencari posisi karakter tertentu di dalam string.
  2. Baris 5 (char nama[32];): array char berkapasitas 32 byte untuk menampung input pengguna.
  3. Baris 7 (if (fgets(nama, sizeof(nama), stdin) != NULL)): fgets menerima array nama, ukuran sizeof(nama) (yaitu 32), dan stream stdin. Argumen ukuran ditulis memakai sizeof agar jika ukuran array berubah, batasan ikut menyesuaikan otomatis. Pemeriksaan != NULL memastikan fgets tidak gagal (misalnya saat EOF).
  4. Baris 9 (nama[strcspn(nama, "\n")] = '\0';): strcspn(nama, "\n") mengembalikan indeks karakter newline pertama di dalam nama, atau panjang string jika newline tidak ditemukan. Kita menimpa posisi tersebut dengan null terminator untuk menghapus newline.
  5. Baris 10 (printf("Halo, %s!\n", nama);): mencetak hasil tanpa karakter newline yang ikut terbawa.

Keamanan Buffer Overflow #

Ketika input pengguna lebih panjang dari kapasitas buffer, fgets tidak pernah menulis melebihi ukuran yang ditentukan. Input yang berlebih akan tetap tersisa di stream stdin dan bisa dibaca oleh panggilan fgets berikutnya. Perilaku ini berbeda dengan gets (yang sudah dihapus dari C11 karena berbahaya) yang menulis tanpa batasan dan rentan serangan buffer overflow.

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

int main(void) {
    char buffer[8];

    /* fgets tidak pernah overflow: input panjang dipotong */
    printf("Masukkan teks: ");
    char *r = fgets(buffer, sizeof(buffer), stdin);
    printf("Hasil       : \"%s\"\n", buffer);
    printf("Return value: %s\n", r == buffer ? "sama dengan buffer" : "NULL");
    printf("Panjang     : %zu karakter\n", strlen(buffer));

    /* Sisa input masih ada di buffer stdin */
    char sisa[64];
    if (fgets(sisa, sizeof(sisa), stdin) != NULL) {
        printf("Sisa input  : \"%s", sisa);
    }
    return 0;
}

Output yang Dihasilkan (input: Belajar bahasa C):

text
Masukkan teks: Hasil       : "Belajar"
Return value: sama dengan buffer
Panjang     : 7 karakter
Sisa input  : " bahasa C

Mari kita telaah baris-baris kode di atas:

  1. Baris 5 (char buffer[8];): array char berkapasitas 8 byte. Maksimum 7 karakter bisa disimpan, ditambah 1 byte untuk null terminator.
  2. Baris 9 (char *r = fgets(buffer, sizeof(buffer), stdin);): fgets dipanggil dengan batas sizeof(buffer) yaitu 8. Return value disimpan di pointer r agar bisa diperiksa.
  3. Baris 10 (printf("Hasil : \"%s\"\n", buffer);): hanya 7 karakter pertama dari input (Belajar) yang masuk ke buffer. Karakter kedelapan diisi null terminator oleh fgets.
  4. Baris 11 (printf("Return value: %s\n", r == buffer ? "..." : "NULL");): jika fgets sukses, nilai kembalian berupa pointer ke buffer yang sama. Jika gagal (misalnya EOF tanpa data), kembalian berupa NULL.
  5. Baris 12 (printf("Panjang : %zu karakter\n", strlen(buffer));): strlen menghitung jumlah karakter sebelum null terminator, yaitu 7.
  6. Baris 15-17 (fgets(sisa, sizeof(sisa), stdin)): sisa input " bahasa C\n" masih ada di stream stdin dan bisa dibaca oleh fgets berikutnya. Karena tidak ada newline di akhir string "Sisa input : ", output " bahasa C muncul tanpa baris baru.

Membaca Banyak Baris #

fgets sangat cocok untuk membaca seluruh isi berkas atau beberapa baris input berturut-turut. Pola umumnya adalah loop while yang terus memanggil fgets sampai return value-nya NULL, yang menandakan akhir input.

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

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

    /* Membaca beberapa baris sampai EOF */
    int nomor = 1;
    while (fgets(baris, sizeof(baris), stdin) != NULL) {
        /* Cek apakah baris terakhir tidak punya newline (input pendek) */
        baris[strcspn(baris, "\n")] = '\0';
        printf("Baris %d: %s\n", nomor++, baris);
    }
    printf("Selesai. Total %d baris terbaca.\n", nomor - 1);
    return 0;
}

Output yang Dihasilkan (input tiga baris: Belajar C, Sangat menyenangkan, Sampai jumpa):

text
Baris 1: Belajar C
Baris 2: Sangat menyenangkan
Baris 3: Sampai jumpa
Selesai. Total 3 baris terbaca.

Mari kita telaah baris-baris kode di atas:

  1. Baris 7 (int nomor = 1;): penghitung nomor baris dimulai dari 1 untuk tampilan yang ramah pengguna.
  2. Baris 8 (while (fgets(baris, sizeof(baris), stdin) != NULL)): setiap iterasi membaca satu baris dari stdin. Loop berhenti ketika fgets mengembalikan NULL, yaitu ketika stdin mencapai EOF (end-of-file).
  3. Baris 10 (baris[strcspn(baris, "\n")] = '\0';): menghapus newline dari setiap baris yang dibaca. Idiom yang sama seperti sebelumnya — aman untuk baris tanpa newline karena strcspn mengembalikan panjang string dalam kasus tersebut.
  4. Baris 11 (printf("Baris %d: %s\n", nomor++, baris);): mencetak nomor baris dan isinya, lalu increment nomor setelah dicetak (post-increment).
  5. Baris 13 (printf("Selesai. Total %d baris terbaca.\n", nomor - 1);): nomor sudah di-increment sekali setelah baris terakhir, jadi total baris yang terbaca adalah nomor - 1.