printf adalah fungsi pertama yang dipelajari setiap programmer C, dan juga fungsi yang manualnya hampir tidak pernah dibaca sampai habis. Fungsi ini layak diberi sepuluh menit, karena format string-nya adalah bahasa kecil tersendiri, dan karena salah menulis specifier bukan sekadar salah ketik: itu undefined behavior.
Fungsi ini ada di <stdio.h>, jadi setiap program yang memakainya diawali #include <stdio.h>.
Model format string
printf menelusuri format string satu karakter setiap kali. Apa pun yang bukan % dicetak persis seperti yang ditulis. % memulai spesifikasi konversi, yang memakan argumen berikutnya dan mencetaknya dalam bentuk yang diminta.
Argumen dicocokkan dengan specifier secara ketat berdasarkan posisi: % pertama mengambil count, yang kedua mengambil name, dan seterusnya. printf tidak punya cara untuk memeriksa apakah kamu mengirim apa yang kamu janjikan, karena ia tidak bisa melihat tipenya. Jadi format string adalah kontrak yang harus kamu tepati. Karena itulah -Wall layak dinyalakan: GCC membaca format string literal saat kompilasi dan memberi peringatan kalau ada argumen yang tidak cocok.
Tidak ada yang memaksa baris baru. printf("Hello") meninggalkan kursor setelah o, dan karena itulah hampir setiap format string diakhiri \n.
Specifier yang kamu pakai sehari-hari
Lima specifier sudah mencakup kebanyakan kode:
| Specifier | Mencetak | Contoh pemanggilan | Output |
|---|---|---|---|
%d | int bertanda | printf("%d", -42) | -42 |
%f | double | printf("%f", 2.5) | 2.500000 |
%s | string (char *) | printf("%s", "hi") | hi |
%c | satu karakter | printf("%c", 'A') | A |
%p | alamat pointer | printf("%p", (void*)&x) | 0x7ffd... |
Ada dua aturan yang tersembunyi di potongan kode itu. % literal ditulis %%: % yang sendirian di akhir format string adalah undefined behavior. Lalu %p membutuhkan void *, jadi lakukan cast pada pointernya; memberikan int * secara langsung secara teknis undefined meskipun dalam praktiknya tercetak dengan baik.
Tabel lengkapnya, yaitu setiap specifier, setiap length modifier, dan kolom untuk scanf, ada di halaman format specifier. Halaman ini hanya membahas yang sering kamu pakai.
Width, precision, dan perataan
Di antara % dan hurufnya, kamu bisa menaruh flag, width, dan precision. Begitulah caramu mengubah sebuah loop menjadi tabel.
Width adalah batas minimum, bukan maksimum: %3d dengan nilai 123456 tetap mencetak keenam digitnya. Precision punya arti yang berbeda untuk setiap tipe: digit setelah titik desimal untuk %f, jumlah karakter maksimum untuk %s, dan jumlah digit minimum untuk %d.
Keduanya bisa diberikan saat runtime dengan *, yang membaca sebuah argumen int:
Mengubah width menyusun ulang seluruh tabel. Inilah cara standar mencetak kolom yang rata di C, tanpa perlu fungsi pembantu untuk menambah spasi pada string.
Mencetak float dengan rapi
%f tanpa precision selalu mencetak enam digit setelah titik, dan itu jarang sekali yang diinginkan orang:
Ada tiga pilihan, dan pedoman untuk masing-masing:
%.Nfuntuk uang, hasil pengukuran, dan apa pun yang punya jumlah desimal yang wajar.%.2fadalah yang kamu butuhkan untuk mata uang.%euntuk notasi ilmiah, selalu dalam bentukd.dddddde±dd.%gsaat kamu tidak tahu besarnya nilai sebelumnya. Specifier ini memilih%fatau%e, mana yang lebih pendek, dan membuang nol di belakang. Inilah pilihan bawaan paling masuk akal untuk mencatat nilai yang belum pernah kamu lihat.
Ada satu jebakan yang perlu dikatakan terus terang: tidak ada %lf di printf. Argumen float dipromosikan menjadi double sebelum printf melihatnya, jadi %f menangani keduanya, dan %lf hanya bermakna di scanf, tempat ukuran tujuan penyimpanannya penting. (C99 memang menerima %lf di printf sebagai sinonim, tapi kebiasaan menulisnya membuat orang menulis %f di scanf, dan itu benar-benar rusak.)
Integer dengan lebar lain
%d adalah untuk int. Tipe yang lebih besar atau unsigned butuh length modifier, dan salah mencocokkannya adalah undefined behavior, bukan sekadar kesalahan pembulatan:
%zu untuk size_t adalah yang paling sering salah: sizeof mengembalikan size_t, bukan int, jadi printf("%d", sizeof x) adalah bug yang kebetulan bekerja di sistem 32-bit dan berperilaku aneh di sistem 64-bit.
Nilai kembalian
printf mengembalikan jumlah karakter yang ditulisnya, atau angka negatif kalau gagal. Nilai ini biasanya diabaikan, dan umumnya itu tidak masalah, tapi sangat berguna saat kamu perlu tahu seberapa lebar output-nya:
Fungsi terkait, sprintf dan snprintf, menulis ke buffer, bukan ke layar, dan mengembalikan panjang yang dihasilkannya. Selalu utamakan snprintf, yang menerima ukuran buffer dan tidak bisa overflow:
snprintf mengembalikan panjang yang seharusnya dibutuhkan string lengkapnya, jadi nilai kembalian yang sama dengan atau lebih besar dari ukuran buffer memberitahumu bahwa output-nya terpotong.
Kerentanan format string
Ini satu-satunya masalah keamanan di printf, dan masalahnya serius. Jangan pernah memberikan data yang tidak kamu tulis sendiri sebagai format string:
/* DANGEROUS - never do this */
printf(user_input);
/* Correct */
printf("%s", user_input);
Kalau user_input berisi %s, printf membaca argumen yang tidak pernah dikirim dan mengikuti sampah berbentuk pointer apa pun yang ditemukannya, biasanya sampai crash. %x yang diulang-ulang menumpahkan isi stack ke layar, yang mungkin berisi password atau kunci. Lalu %n, yang menulis jumlah karakter ke sebuah argumen, dulu mengubah masalah ini menjadi kerusakan memori sembarang dan eksekusi kode jarak jauh.
Aturannya mutlak dan tidak ada biayanya: format string selalu berupa literal yang kamu tulis sendiri. Apa pun yang berubah-ubah masuk sebagai argumen. -Wformat-security dari GCC menandai kesalahan ini, dan flag itu sudah termasuk di -Wall -Wextra.
Buffering: kenapa output kadang muncul terlambat
printf tidak langsung menulis ke terminal. Output-nya di-buffer, lalu dikeluarkan saat buffer penuh, saat ada baris baru di terminal, atau saat program keluar dengan normal. Ada dua akibatnya:
- Program yang crash bisa kehilangan output yang "sudah dicetak". Kalau kamu melakukan debug dengan
printfdan pesan terakhir tidak pernah muncul, crash-nya terjadi setelah baris itu, bukan sebelumnya. Tambahkanfflush(stdout)atau kirim output debug kestderr, yang tidak di-buffer. fprintf(stderr, ...)adalah cara yang benar untuk mencetak error. Stream ini tidak di-buffer, dan merupakan stream terpisah yang bisa dialihkan pengguna secara terpisah dari output normal.
Pertanyaan yang sering diajukan
Bagaimana cara kerja printf di C?
printf menerima format string dan memindainya karakter demi karakter. Karakter biasa dicetak apa adanya; setiap % memulai spesifikasi konversi yang memakai salah satu argumen berikutnya dan mencetaknya dalam bentuk yang diminta. printf("%d apples\n", 5) mencetak 5 apples dan baris baru.
Bagaimana cara mencetak float dengan 2 angka di belakang koma di C?
Pakai precision: printf("%.2f\n", 3.14159) mencetak 3.14. Angka setelah titik menentukan berapa digit yang muncul setelah titik desimal. %f tanpa precision selalu mencetak 6 digit, dan karena itulah float tanpa format terlihat begitu berantakan.
Kenapa printf mencetak sampah atau membuat program crash?
Hampir selalu karena specifier dan argumennya tidak cocok. printf("%d", 3.5) atau printf("%s", 42) adalah undefined behavior: printf tidak bisa melihat tipe yang sebenarnya, ia mempercayai format string. Kompilasi dengan -Wall, yang membuat GCC memeriksa format terhadap argumennya.
Kenapa printf(user_input) berbahaya?
Kalau teks dari pengguna berisi urutan %, printf akan menjalankannya dan membaca argumen yang tidak pernah dikirim: membocorkan isi memori stack, dan dengan %n bahkan menulis ke memori. Inilah kerentanan format string yang klasik. Selalu tulis printf("%s", user_input) sebagai gantinya.