Format specifier memberi tahu printf cara mengubah nilai menjadi teks, dan memberi tahu scanf cara mengubah teks menjadi nilai. Inilah satu bagian C di mana compiler secara default tidak bisa membantumu, karena tipe argumen tidak terlihat oleh fungsinya. Jadi halaman ini adalah tabel untuk dijadikan pegangan.
Bentuk umum sebuah spesifikasi adalah:
%[flags][width][.precision][length]conversion
Hanya % dan huruf konversi yang wajib. %-8.2lf memakai kelima bagiannya.
Tabel Konversi
| Specifier | Arti | Argumen printf | Argumen scanf |
|---|---|---|---|
%d | integer desimal signed | int | int * |
%i | integer signed | int (sama dengan %d) | int * (juga menerima 0x, oktal berawalan 0) |
%u | desimal unsigned | unsigned int | unsigned int * |
%f | floating point desimal | double | float * |
%F | seperti %f, INF/NAN huruf besar | double | - |
%e / %E | notasi ilmiah | double | float * |
%g / %G | yang lebih pendek dari %f / %e | double | float * |
%a / %A | floating point heksadesimal | double | float * |
%c | satu karakter | int (char yang dinaikkan) | char * |
%s | string | char * (diakhiri '\0') | char * (buffer) |
%p | alamat pointer | void * | void ** |
%x / %X | heksadesimal unsigned | unsigned int | unsigned int * |
%o | oktal unsigned | unsigned int | unsigned int * |
%n | menyimpan jumlah karakter yang sudah ditulis | int * | int * |
%% | tanda % apa adanya | tidak ada | mencocokkan % apa adanya |
%[...] | scanset (hanya scanf) | - | char * |
Dua entri perlu diberi peringatan. %n menulis ke memori alih-alih mencetak, dan merupakan mekanisme di balik celah keamanan format string: jangan pernah biarkan teks dari pengguna masuk ke format string. Lalu %p membutuhkan void *; memberikan int * langsung adalah undefined behavior, meski setiap compiler sungguhan tetap mencetak alamatnya.
Length Modifier
Length modifier ditaruh di antara presisi dan huruf konversi, dan menyatakan seberapa besar argumennya sebenarnya.
Konversi signed (d i) dan unsigned (u x o) menerima tipe yang berbeda: %lu menginginkan unsigned long, bukan long, dan salah memilihnya adalah undefined behavior, bukan sekadar kesalahan kosmetik.
| Modifier | Dengan d i (printf / scanf) | Dengan u x o (printf / scanf) |
|---|---|---|
hh | int (dari signed char) / signed char * | unsigned int (dari unsigned char) / unsigned char * |
h | int (dari short) / short * | unsigned int (dari unsigned short) / unsigned short * |
| (tidak ada) | int / int * | unsigned int / unsigned int * |
l | long / long * | unsigned long / unsigned long * |
ll | long long / long long * | unsigned long long / unsigned long long * |
z | tipe ukuran signed / pointer ke sana | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | pasangan unsigned dari ptrdiff_t |
Dan untuk keluarga floating-point dan karakter:
| Modifier | Dengan | Tipe printf | Tipe scanf |
|---|---|---|---|
| (tidak ada) | f e g a | double | float * |
l | f e g a | double (diterima, tanpa efek) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
Kombinasi yang benar-benar akan kamu ketik:
%zu adalah yang wajib dihafal. sizeof, strlen, dan setiap ukuran dari library standar mengembalikan size_t, yang berukuran 8 byte di sistem 64-bit sementara int berukuran 4. printf("%d", strlen(s)) adalah bug sungguhan yang terlihat berfungsi di sebagian platform; gunakan %zu.
Modifier hh dan h terutama ada untuk scanf, di mana ukuran tujuan itu penting. Di printf, short dinaikkan menjadi int sebelum pemanggilan, jadi %d toh akan mencetaknya dengan benar; %hd hanya mendokumentasikan maksudnya.
Tipe dengan Lebar Pasti
Untuk tipe <stdint.h> seperti int32_t, specifier yang portabel adalah macro dari <inttypes.h>:
PRId32 dijabarkan menjadi huruf yang tepat untuk platformmu, dan string literal yang bersebelahan otomatis digabung, karena itulah format-nya dipecah menjadi tiga bagian. Bentuknya jelek; alternatifnya, melakukan cast ke long long lalu memakai %lld, sering kali lebih mudah dibaca dan selalu benar.
Flag
Flag ditaruh tepat setelah % dan mengubah tampilannya. Flag hanya berlaku untuk printf.
| Flag | Efek | Contoh | Output |
|---|---|---|---|
- | padding di belakang nilai, bukan di depannya | %-6d| pada 42 | 42 | |
+ | selalu cetak tanda untuk nilai signed | %+d pada 42 | +42 |
| spasi | cetak spasi di tempat + akan muncul | % d pada 42 | 42 |
0 | padding dengan nol, bukan spasi | %06d pada 42 | 000042 |
# | bentuk alternatif: 0x untuk %x, 0 untuk %o, pertahankan titik desimal untuk %g | %#x pada 255 | 0xff |
Flag bisa digabung: %-+8.2f berarti lebar minimum 8, dua desimal, selalu bertanda, dengan padding di belakang angka. - mengalahkan 0 kalau kamu menulis keduanya.
Lebar dan Presisi
Lebar adalah ukuran field minimum: output diberi padding untuk mencapainya, dan tidak pernah dipotong agar muat. Presisi (setelah titik) punya arti berbeda untuk setiap keluarga:
| Konversi | Arti presisi |
|---|---|
%f %e | digit setelah titik desimal (default 6) |
%g | total digit signifikan (default 6) |
%s | jumlah karakter maksimum yang dicetak |
%d %i %u %x %o | jumlah digit minimum, diberi padding nol |
* di tempat salah satu angka itu mengambil nilainya dari argumen int, yang dibaca sebelum nilainya sendiri. Begitulah cara membuat tabel yang lebar kolomnya dihitung saat program berjalan.
Di scanf, lebar punya tugas yang berbeda dan jauh lebih penting: ia membatasi berapa banyak input yang dipakai sebuah konversi, satu-satunya hal yang menghalangi %s dari buffer overflow.
Di Mana printf dan scanf Berbeda
Keduanya terlihat mirip tetapi berbeda di empat hal. Masing-masing adalah sumber bug yang nyata:
| Situasi | printf | scanf |
|---|---|---|
Nilai double | %f (float dinaikkan menjadi double) | %lf, karena %f berarti float * |
| Lebar | ukuran field minimum, diberi padding | input maksimum yang dipakai, dipotong |
| Argumen | nilai | pointer: &x, atau nama array |
| Spasi di format | mencetak spasi | melewati deretan whitespace apa pun |
Coba 2.5 hello. Format yang membacanya dan format yang mencetaknya memakai huruf berbeda untuk double yang sama, dan 15 di %15s membatasi input sampai 15 karakter ditambah terminator, persis seukuran word.
Scanset di scanf
%[...] membaca sekumpulan karakter, bukan kata yang dibatasi whitespace. Inilah cara membaca satu baris penuh dengan scanf, meski fgets tetap alat yang lebih baik:
[^\n] berarti "karakter apa pun kecuali baris baru". %[0-9] hanya akan membaca digit, %[abc] hanya tiga huruf itu. Berbeda dengan %s, scanset tidak melewati whitespace di depan, jadi satu baris baru yang tersisa di buffer membuatnya sama sekali tidak cocok dengan apa pun.
Salah Memilih Adalah Undefined Behavior
printf tidak bisa melihat tipe asli argumenmu. Ia membaca byte sebanyak yang diisyaratkan specifier, dan menafsirkannya sesuai kata specifier itu. Jadi ketidakcocokan bukan sekadar gangguan format, melainkan membaca memori sebagai sesuatu yang salah:
printf("%d\n", 3.5); /* prints garbage: reads a double's bits as int */
printf("%s\n", 42); /* treats 42 as an address - crash */
printf("%d\n", strlen(s)); /* size_t read as int - wrong on 64-bit */
printf("%f\n", 3); /* int read as double - garbage */
Pertahanannya hanya butuh satu flag. Kompilasi dengan -Wall dan GCC memeriksa setiap format string literal terhadap argumennya:
gcc -Wall -Wextra program.c -o program
Compiler melaporkan keempat baris di atas saat kompilasi. Ini peringatan paling berharga di C, dan sudah menyala secara default di editor di atas.
Untuk sisi praktis memakai semua ini, seperti tabel yang rata, mencetak float agar mudah dibaca, dan membaca input tanpa bug baris baru yang klasik, lihat printf dan scanf.
Pertanyaan yang sering diajukan
Apa arti %d di C?
%d adalah conversion specifier untuk integer desimal signed, yaitu int. Di printf, ia mencetak argumen sebagai digit basis 10; di scanf, ia membaca digit basis 10 ke dalam int *. %i artinya sama di printf, tetapi di scanf %i juga menerima heksadesimal 0x dan oktal yang diawali nol.
Apa perbedaan %f dan %lf di C?
Di printf praktis tidak ada perbedaan: argumen float dinaikkan menjadi double, jadi %f mencetak keduanya, dan C99 menerima %lf sebagai sinonimnya. Di scanf perbedaannya sangat penting: %f menulis ke float * (4 byte) dan %lf ke double * (8 byte). Memakai %f untuk double merusak memori.
Bagaimana cara mencetak size_t atau long long di C?
%zu untuk size_t (yang dikembalikan sizeof dan strlen) dan %lld / %llu untuk long long / unsigned long long. Memakai %d untuk tipe-tipe ini adalah undefined behavior: kebetulan berfungsi di sistem 32-bit dan bermasalah di sistem 64-bit, karena itulah bug ini bisa bertahan begitu lama.
Apa yang terjadi kalau memakai format specifier yang salah di C?
Itu undefined behavior, bukan sekadar kesalahan pembulatan. printf membaca argumen sesuai klaim specifier-nya, jadi printf("%d", 3.5) mencetak nilai sampah dan printf("%s", 42) mengikuti angka 42 sebagai alamat dan biasanya crash. Kompilasi dengan -Wall, yang membuat GCC memeriksa format string terhadap argumennya.