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.
#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"):
Masukkan nama: Halo, BudiPada 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).
#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"):
Masukkan nama: Halo, Budi (panjang 4)Mari kita telaah baris-baris kode di atas:
- Baris 9 (
scanf("%9s", nama);): angka9di antara%dansadalah batas lebar.scanfmembaca maksimal 9 karakter non-whitespace, lalu menambahkan null terminator. Buffernama(10 byte) terisi penuh tanpa overflow. - Baris 11 (
printf("Halo, %s (panjang %zu)\n", ...)): meskipun input adalah"Budi Santoso"(12 karakter),scanfberhenti di spasi setelah"Budi"karena%sberhenti di whitespace. Hanya 4 karakter yang tersimpan dinama, dan karakter sisanya (" Santoso") tetap berada di stdin buffer. - Konsekuensi: input yang lebih panjang dari 9 karakter akan terpotong tanpa overflow. Namun karakter sisa tetap ada di stdin dan bisa terbaca oleh pemanggilan
scanfberikutnya—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.
#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"):
Masukkan satu baris: Tersimpan: [Pemrograman C m]
Panjang: 15 (maks 15)Mari kita telaah baris-baris kode di atas:
- Baris 8 (
fgets(buf, sizeof buf, stdin)): argumen kedua adalah ukuran buffer.fgetsmembaca maksimalsizeof buf - 1karakter (15 karakter) lalu menambahkan null terminator. Argumen ketigastdinadalah stream sumber. Fungsi mengembalikanbufbila berhasil, atauNULLbila end-of-file atau terjadi error—karena itu selalu cek!= NULL. - Baris 10 (
buf[strcspn(buf, "\n")] = '\0';): ini adalah trik kunci.strcspn(buf, "\n")mengembalikan indeks karakter newline pertama dibuf, atau panjang string bila tidak ada newline. Kita meng-overwrite posisi tersebut dengan'\0'. Efeknya: bilafgetsmenyertakan 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 manualif (n > 0 && buf[n-1] == '\n'). - 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.fgetsmenghormati batas buffer. - Baris 13 (
printf("Panjang: %zu (maks %zu)\n", strlen(buf), sizeof buf - 1)): panjang tersimpan adalah 15, yang juga adalahsizeof buf - 1. Ini menandakan input terpotong—buffer tidak cukup untuk menampung seluruh baris. Karakter sisanya tetap ada di stdin dan bisa dibaca di pemanggilanfgetsberikutnya.
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.