scanf adalah cerminan printf: header yang sama, format string yang mirip, arah yang berlawanan. Fungsi ini membaca teks dari standard input, mengonversinya sesuai format, lalu menyimpan hasilnya lewat pointer yang kamu berikan.
Fungsi ini juga yang paling sering membuat pemula bingung di C, karena tiga perilakunya mengejutkan dan salah satunya adalah celah keamanan. Halaman ini membahas keempatnya.
Bentuk dasarnya, dan kenapa &
Ketik sebuah angka lalu tekan Enter untuk menjalankannya. &age bukan hiasan. C mengirim setiap argumen berdasarkan nilai, jadi kalau kamu menulis scanf("%d", age), fungsi itu akan menerima salinan nilai age saat ini (sampah) dan tidak punya cara untuk menjangkau variabel itu sendiri. &age mengirim alamat variabel, lalu scanf menulis lewat alamat itu. Lihat halaman pointer untuk gambaran lengkapnya.
Lupa menulis & adalah kesalahan scanf yang paling umum. Compiler dengan -Wall menangkapnya (format '%d' expects argument of type 'int *'); tanpa peringatan yang menyala, kodenya bisa dikompilasi dan merusak memori di alamat acak.
Membaca beberapa nilai
Satu pemanggilan bisa membaca beberapa nilai. Whitespace di input, yaitu spasi, tab, dan baris baru, memisahkan nilai-nilainya, dan specifier angka melewati whitespace sebanyak apa pun.
Spasi literal di "%d %d %d" sebenarnya berlebihan, karena %d sudah melewati whitespace di depan, tapi spasi itu membuat formatnya mudah dibaca. Yang tidak berlebihan adalah memeriksa nilai kembaliannya, yang dibahas di bawah.
Karakter yang bukan whitespace di format harus cocok persis dengan input, dan begitulah caramu membaca input yang terstruktur:
: di format menuntut adanya : di input. Ketik 9:30 dan input-nya terbaca; ketik 9 30 dan pembacaannya gagal setelah membaca 9.
Selalu periksa nilai kembaliannya
scanf mengembalikan jumlah item yang berhasil diisi, bukan jumlah karakter, dan bukan tanda berhasil. Fungsi ini mengembalikan EOF kalau input berakhir sebelum ada yang dikonversi.
Ketik hello di prompt. scanf mengembalikan 0, n dibiarkan tidak tersentuh, masih menyimpan sampah apa pun yang ada sejak awal, dan teks penyebab masalahnya masih tertinggal di stream input. Bagian terakhir itulah yang mengubah satu input buruk menjadi loop tak berujung:
/* BUG: spins forever on non-numeric input */
while (scanf("%d", &n) != 1) {
printf("Try again: ");
}
scanf tidak pernah menghabiskan teks yang tidak bisa dikonversinya, jadi pemanggilan berikutnya gagal pada karakter yang sama, selamanya. Untuk memulihkannya, kamu harus membuang sisa barisnya sendiri:
Pengecekan EOF itu penting: saat stream input berakhir (pengguna menekan Ctrl+D, atau file yang dialirkan lewat pipe sudah habis), scanf mengembalikan EOF terus-menerus, jadi loop yang hanya menguji != 1 akan mencoba ulang selamanya. Tangani "input buruk" dan "tidak ada input lagi" sebagai dua kasus yang memang berbeda. Dengan begitu, fungsi pembantu discard_line layak disimpan di program mana pun yang memakai scanf untuk input dari manusia.
Buffer overflow pada %s
%s membaca karakter ke dalam array char. Perhatikan tidak ada &: nama array sudah berubah (decay) menjadi pointer ke elemen pertamanya, jadi &name akan bertipe salah.
Masalahnya, %s biasa tidak tahu seberapa besar array-mu:
char name[10];
scanf("%s", name); /* DANGEROUS: types 40 characters, writes 41 bytes */
Tidak ada yang menghentikan penulisan di ujung array. Penulisannya terus berlanjut ke apa pun yang ada berikutnya di memori, seperti variabel lain atau alamat kembali. Inilah buffer overflow klasik, dan secara historis kelas bug yang paling banyak dieksploitasi di C. Perbaikannya adalah width di antara % dan s, satu lebih kecil dari ukuran array untuk menyisakan tempat bagi '\0' penutup:
Width-nya harus ditulis sebagai literal di format string, sehingga merepotkan untuk menjaganya tetap sesuai dengan ukuran array. Satu alasan lagi kenapa kebanyakan C di production memakai fgets.
Kejutan kedua dari %s: specifier ini berhenti di whitespace pertama. Ketik Ada Lovelace, maka name berisi Ada; Lovelace menunggu di stream untuk pembacaan berikutnya. %s membaca sebuah kata, bukan sebuah baris.
Bug baris baru yang tertinggal
Inilah "scanf tidak berfungsi" yang dialami semua orang. Specifier angka melewati whitespace di depan; %c tidak.
Beginilah yang terjadi tanpa spasi itu. Kamu mengetik 30 lalu menekan Enter. %d menghabiskan 3 dan 0 lalu berhenti di baris baru, meninggalkan '\n' di buffer. scanf("%c", &initial) berikutnya membaca baris baru itu sebagai karakternya dan langsung kembali: prompt-nya muncul dan terlewati dalam sekejap.
Spasi di format string scanf berarti "lewati whitespace sebanyak apa pun di sini", jadi " %c" melangkahi baris baru yang nyasar itu lalu menunggu karakter sungguhan. Spasi itu hanya dibutuhkan sebelum %c dan %[; specifier lain sudah melewati whitespace sendiri.
Membaca float dan char
Specifier-nya sebagian besar sama dengan printf, dengan satu perbedaan penting: scanf perlu tahu ukuran tujuan penyimpanannya dengan tepat, karena ia menulis lewat pointer.
Coba 1.5 2.25 7 x.
%f membaca float, %lf membaca double. Di printf keduanya dicetak dengan cara yang sama karena float dipromosikan ke double sebelum pemanggilan, tapi di scanf tidak ada promosi, dan memakai %f dengan double * menulis empat byte ke variabel delapan byte sehingga nilainya rusak. %c di sini tidak butuh spasi di depan karena %d sebelumnya sudah berhenti di spasi, bukan di baris baru. (Tabel lengkapnya ada di halaman format specifier.)
fgets: alternatif yang tangguh
Untuk apa pun yang diketik manusia, baca seluruh barisnya lalu parse sesudahnya. fgets menerima ukuran buffer sebagai argumen, jadi secara fisik tidak bisa overflow, dan kalau barisnya muat, fungsi ini ikut menghabiskan baris barunya sehingga tidak ada yang tertinggal untuk membingungkan pembacaan berikutnya. (Baris yang lebih panjang dari buffer meninggalkan ekornya belum terbaca, dan '\n' yang tidak ada di hasilmu adalah cara mendeteksinya.)
Tiga hal tentang pola itu:
sizeof linemengirim ukuran yang sebenarnya, jadi mengubah ukuran array tidak butuh perubahan lain, berbeda dengan literal%19s.fgetsmenyimpan baris baru kalau barisnya muat, dan karena itulah ada pemangkasan denganstrcspn. Itu juga cara mendeteksi baris yang terlalu panjang untuk buffer: tidak ada'\n'di hasil yang kamu dapat.- Parse sesudahnya.
strtoluntuk integer (fungsi ini juga bisa memberitahumu di mana parsing berhenti),strtoduntuk floating point, atausscanf(line, "%d %d", &a, &b)untuk memakai format scanf pada string yang sudah kamu pegang dengan aman.
sscanf layak disebut tersendiri: fungsi ini adalah scanf yang membaca dari string, bukan dari stream input, dan berpadu sempurna dengan fgets. Kamu mendapat parsing yang praktis ala scanf tanpa masalah stream-nya sama sekali.
Kesalahan umum
- Lupa
&pada argumen yang bukan array. Kompilasi dengan-Wall. %stanpa width. Buffer overflow yang tinggal menunggu input yang panjang.- Mengabaikan nilai kembalian. Lalu membaca variabel yang tidak pernah diisi
scanf. - Melakukan loop pada
scanfyang gagal tanpa membuang input. Loop tak berujung. %ctanpa spasi di depan setelah membaca angka. Bug prompt yang terlewati.- Memakai
%funtukdouble. Kerusakan diam-diam;scanfbutuh%lf. - Mengira
%smembaca satu baris. Specifier ini hanya membaca satu kata yang dibatasi whitespace.
Pertanyaan yang sering diajukan
Kenapa scanf butuh & di C?
Karena C mengirim argumen berdasarkan nilai. scanf harus mengubah variabelmu, jadi butuh alamat variabel itu, bukan salinan nilainya: scanf("%d", &age). Satu-satunya pengecualian adalah array karakter, yang namanya sudah berupa alamat: scanf("%19s", name) tidak memakai &.
Kenapa scanf melewati input untuk char atau string?
scanf("%d", ...) sebelumnya meninggalkan karakter baris baru dari tombol Enter di buffer input. %d dan %f melewati whitespace di depan, tapi %c tidak, jadi specifier itu membaca baris baru itu alih-alih menunggu. Perbaikannya adalah spasi di format, scanf(" %c", &ch), yang memberi tahu scanf untuk melewati whitespace lebih dulu.
Bagaimana cara membaca string dengan scanf secara aman di C?
Beri %s width maksimum yang satu lebih kecil dari buffer-mu: char name[20]; scanf("%19s", name);. Tanpa width, %s menulis sebanyak karakter yang diketik pengguna dan dengan senang hati melewati ujung array. Ingat juga bahwa %s berhenti di whitespace pertama, jadi tidak bisa membaca nama lengkap yang mengandung spasi.
Sebaiknya pakai scanf atau fgets di C?
Pakai fgets untuk apa pun yang diketik manusia. Fungsi ini membaca satu baris utuh ke buffer berukuran tertentu, tidak bisa overflow, dan (kalau barisnya muat) ikut menghabiskan karakter baris baru, sehingga bug klasik input campuran hilang. Parse barisnya sesudahnya dengan sscanf atau strtol. scanf cukup untuk latihan kecil dan input mesin yang formatnya rapi.