Apa penggunaan specifier format% n di C?


125

Apa gunanya %npenentu format dalam C? Adakah yang bisa menjelaskan dengan contoh?


25
Apa yang terjadi dengan seni membaca manual yang bagus?
Jens

8
Saya pikir pertanyaan sebenarnya adalah apa POINT dari opsi seperti ini? mengapa ada yang mau tahu nilai angka char yang dicetak apalagi tulis nilai itu langsung ke memori. Sepertinya para pengembang bosan dan memutuskan untuk memperkenalkan bug ke dalam kernal
jia chen

Itu sebabnya Bionic melepaskannya.
solidak

1
Ini sebenarnya adalah pertanyaan yang valid, dan salah satu yang tidak akan dijawab oleh manual yang baik; telah ditemukan bahwa %nmembuat printfTuring-selesai secara tidak sengaja dan Anda dapat misalnya mengimplementasikan Brainfuck di dalamnya, lihat github.com/HexHive/printbf dan oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Jawaban:


147

Tidak ada yang dicetak. Argumen harus berupa penunjuk ke int yang ditandatangani, tempat jumlah karakter yang ditulis sejauh ini disimpan.

#include <stdio.h>

int main()
{
  int val;

  printf("blah %n blah\n", &val);

  printf("val = %d\n", val);

  return 0;

}

Kode sebelumnya dicetak:

blah  blah
val = 5

1
Anda menyebutkan bahwa argumen harus berupa penunjuk ke int yang ditandatangani, lalu Anda menggunakan int yang tidak ditandai dalam contoh Anda (mungkin hanya salah ketik).
bta

1
@AndrewS: Karena fungsi akan mengubah nilai variabel.
Jack

3
@Jack intselalu masuk.
jamesdlin

1
@ jamesdlin: Kesalahan saya. Maaf .. saya tidak tahu di mana saya membaca itu.
Jack

1
Untuk beberapa alasan sampel menimbulkan kesalahan dengan catatan n format specifies disabled. Apa alasannya?
Johnny_D

186

Sebagian besar jawaban ini menjelaskan apa yang %n dilakukan (yaitu untuk mencetak apa-apa dan untuk menulis jumlah karakter yang dicetak sejauh ini ke intvariabel), tetapi sejauh ini tidak ada yang benar-benar memberikan contoh dari apa yang digunakan . Ini satu:

int n;
printf("%s: %nFoo\n", "hello", &n);
printf("%*sBar\n", n, "");

akan dicetak:

hello: Foo
       Bar

dengan Foo and Bar sejajar. (Sangat sepele untuk melakukannya tanpa menggunakan %nuntuk contoh khusus ini, dan secara umum seseorang selalu dapat memutuskan printfpanggilan pertama :

int n = printf("%s: ", "hello");
printf("Foo\n");
printf("%*sBar\n", n, "");

Apakah sedikit menambah kenyamanan layak menggunakan sesuatu seperti esoteris %n(dan mungkin memperkenalkan kesalahan) terbuka untuk diperdebatkan.)


3
Ya ampun - ini adalah versi berbasis karakter menghitung ukuran piksel string dalam font yang diberikan!

Bisakah Anda menjelaskan mengapa & n dan * diperlukan. Apakah mereka berdua petunjuk?
Andrew S

9
@AndrewS &nadalah pointer ( &adalah alamat-operator); sebuah pointer diperlukan karena C adalah nilai by-pass, dan tanpa sebuah pointer, printftidak dapat mengubah nilai n. The %*spenggunaan dalam printfformat string mencetak %sspecifier (dalam hal ini string kosong "") menggunakan lebar lapangan dari nkarakter. Penjelasan printfprinsip - prinsip dasar pada dasarnya di luar ruang lingkup pertanyaan ini (dan jawaban); Saya akan merekomendasikan membaca printfdokumentasi atau mengajukan pertanyaan Anda sendiri pada SO.
jamesdlin

3
Terima kasih telah menunjukkan kasus penggunaan. Saya tidak mengerti mengapa orang hanya menyalin-tempel manual ke SO dan menulis ulang terkadang. Kita adalah manusia dan semuanya dilakukan karena suatu alasan yang harus selalu dijelaskan dalam sebuah jawaban. "Tidak melakukan apa-apa" seperti mengatakan "Kata keren berarti keren" - pengetahuan yang hampir tidak berguna.
the_endian

1
@PSkocik Ini berbelit-belit dan cukup rentan kesalahan tanpa menambahkan tingkat tipuan ekstra.
jamesdlin

18

Saya belum benar-benar melihat banyak penggunaan %nspecifier yang praktis di dunia nyata , tetapi saya ingat bahwa itu digunakan pada kerentanan oldfool printf dengan serangan format string beberapa waktu yang lalu.

Sesuatu yang seperti ini

void authorizeUser( char * username, char * password){

    ...code here setting authorized to false...
    printf(username);

    if ( authorized ) {
         giveControl(username);
    }
}

di mana pengguna jahat dapat mengambil keuntungan dari parameter nama pengguna yang diteruskan ke printf sebagai string format dan menggunakan kombinasi %d, %catau w / e untuk pergi melalui tumpukan panggilan dan kemudian memodifikasi variabel yang diotorisasi ke nilai sebenarnya.

Ya itu penggunaan esoteris, tetapi selalu berguna untuk mengetahui saat menulis daemon untuk menghindari celah keamanan? : D


1
Ada lebih banyak alasan daripada %nmenghindari menggunakan string input yang tidak dicentang sebagai printfstring format.
Keith Thompson

13

Dari sini kita melihat bahwa ia menyimpan jumlah karakter yang dicetak sejauh ini.

n Argumen harus menjadi pointer ke integer yang ditulis jumlah byte yang ditulis ke output sejauh ini oleh panggilan ini ke salah satu fprintf()fungsi. Tidak ada argumen yang dikonversi.

Contoh penggunaannya adalah:

int n_chars = 0;
printf("Hello, World%n", &n_chars);

n_charsmaka akan memiliki nilai 12.


10

Sejauh ini semua jawaban tentang itu %ntidak, tetapi tidak mengapa ada orang yang menginginkannya sejak awal. Saya menemukan ini agak berguna dengan sprintf/ snprintf, ketika Anda mungkin perlu memecah atau memodifikasi string yang dihasilkan, karena nilai yang disimpan adalah indeks array ke dalam string yang dihasilkan. Aplikasi ini jauh lebih berguna, dengan sscanf, terutama karena fungsi dalam scanfkeluarga tidak mengembalikan jumlah karakter yang diproses tetapi jumlah bidang.

Penggunaan lain yang benar-benar meretas adalah mendapatkan pseudo-log10 secara gratis pada saat yang sama saat mencetak nomor sebagai bagian dari operasi lain.


+1 untuk menyebutkan kegunaan untuk %n, meskipun saya mohon berbeda tentang "semua jawaban ...". = P
jamesdlin

1
Orang-orang jahat terima kasih telah menggunakan printf /% n, sprintf, dan sscanf;)
jww

6
@noloader: Bagaimana bisa begitu? Penggunaan %n benar-benar nol bahaya kerentanan terhadap penyerang. Kekeliruan yang salah tempat itu %nsebenarnya berasal dari praktik bodoh mengirimkan string pesan daripada string format sebagai argumen format. Situasi ini tentu saja tidak pernah muncul ketika %nsebenarnya merupakan bagian dari format string yang sedang digunakan.
R .. GitHub BERHENTI MEMBANTU ICE

% n memungkinkan Anda menulis ke memori. Saya pikir Anda mengasumsikan bahwa penyerang tidak mengontrol pointer itu (saya bisa saja salah). Jika penyerang mengontrol pointer (itu hanya parameter lain untuk printf), ia dapat melakukan penulisan 4 byte. Apakah dia dapat untung atau tidak adalah cerita yang berbeda.
jww

8
@noloader: Itu benar tentang penggunaan pointer. Tidak ada yang mengatakan "orang jahat terima kasih" untuk menulis *p = f();. Mengapa harus %n, yang hanya merupakan cara lain untuk menulis hasil ke objek yang ditunjuk oleh pointer, dianggap "berbahaya", daripada menganggap pointer itu sendiri berbahaya?
R .. GitHub BERHENTI MEMBANTU ICE

10

Argumen yang terkait dengan %nakan diperlakukan sebagai int*dan diisi dengan jumlah total karakter yang dicetak pada saat itu di printf.


9

Suatu hari saya menemukan diri saya dalam situasi di mana %ndengan baik akan menyelesaikan masalah saya. Tidak seperti jawaban saya sebelumnya , dalam hal ini, saya tidak dapat menemukan alternatif yang baik.

Saya memiliki kontrol GUI yang menampilkan beberapa teks tertentu. Kontrol ini dapat menampilkan bagian teks itu dengan huruf tebal (atau miring, atau bergaris bawah, dll.), Dan saya bisa menentukan bagian mana dengan menentukan indeks karakter awal dan akhir.

Dalam kasus saya, saya membuat teks dengan kontrol snprintf, dan saya ingin salah satu dari substitusi dibuat tebal. Menemukan indeks awal dan akhir untuk substitusi ini tidak sepele karena:

  • String berisi banyak pergantian, dan salah satu dari pergantian tersebut adalah sewenang-wenang, teks yang ditentukan pengguna. Ini berarti bahwa melakukan pencarian teks untuk substitusi yang saya pedulikan berpotensi ambigu.

  • String format mungkin dilokalkan, dan mungkin menggunakan $ekstensi POSIX untuk penentu format format. Oleh karena itu mencari string format asli untuk penspesifikasi format itu sendiri tidak mudah.

  • Aspek lokalisasi juga berarti bahwa saya tidak dapat dengan mudah memecah string format menjadi beberapa panggilan snprintf.

Oleh karena itu cara paling mudah untuk menemukan indeks di sekitar substitusi tertentu adalah dengan melakukan:

char buf[256];
int start;
int end;

snprintf(buf, sizeof buf,
         "blah blah %s %f yada yada %n%s%n yakety yak",
         someUserSpecifiedString,
         someFloat,
         &start, boldString, &end);
control->set_text(buf);
control->set_bold(start, end);

Saya akan memberi Anda +1 untuk case use. Tetapi Anda akan gagal audit, jadi Anda mungkin harus menemukan cara lain untuk menandai awal dan akhir teks tebal. Sepertinya tiga snprintfsambil memeriksa nilai kembali akan berfungsi dengan baik karena snprintfmengembalikan jumlah karakter yang ditulis. Mungkin sesuatu seperti: int begin = snprintf(..., "blah blah %s %f yada yada", ...);dan int end = snprintf(..., "%s", ...);kemudian ekor: snprintf(..., "blah blah");.
jww

3
@jww Masalah dengan beberapa snprintfpanggilan adalah bahwa penggantian mungkin diatur ulang di lokal lain, sehingga mereka tidak dapat dipecah seperti itu.
jamesdlin

Terima kasih untuk contohnya. Tapi tidak bisakah Anda, seperti, menulis urutan kontrol terminal untuk membuat output tebal tepat sebelum bidang dan kemudian menulis urutan setelahnya? Jika Anda tidak melakukan hardcode pada urutan kontrol terminal, Anda dapat membuatnya juga posisional (dapat dipesan ulang).
PSkocik

1
@PSkocik Jika Anda mengeluarkan ke terminal. Jika Anda bekerja dengan, katakanlah, kontrol edit kaya Win32, itu tidak akan membantu kecuali jika Anda ingin kembali dan mem-parsing urutan kontrol terminal sesudahnya. Itu juga mengasumsikan bahwa Anda ingin menghormati urutan kontrol terminal di sisa teks yang diganti; jika tidak, maka Anda harus memfilter atau menghindarinya. Saya tidak mengatakan bahwa tidak mungkin melakukannya tanpa %n; Saya mengklaim bahwa menggunakan %nlebih mudah daripada alternatif.
jamesdlin

1

Itu tidak mencetak apa pun. Ini digunakan untuk mengetahui berapa banyak karakter yang dicetak sebelum %nmuncul dalam format string, dan output ke int yang disediakan:

#include <stdio.h>

int main(int argc, char* argv[])
{
    int resultOfNSpecifier = 0;
    _set_printf_count_output(1); /* Required in visual studio */
    printf("Some format string%n\n", &resultOfNSpecifier);
    printf("Count of chars before the %%n: %d\n", resultOfNSpecifier);
    return 0;
}

( Dokumentasi untuk_set_printf_count_output )


0

Ini akan menyimpan nilai jumlah karakter yang dicetak sejauh ini dalam printf()fungsi itu.

Contoh:

int a;
printf("Hello World %n \n", &a);
printf("Characters printed so far = %d",a);

Output dari program ini adalah

Hello World
Characters printed so far = 12

ketika saya mencoba kode Anda itu memberi saya: Karakter Hello World dicetak sejauh ini = 36 ,,,,, mengapa 36?! Saya menggunakan GCC 32bit di mesin windows.
Sina Karvandi

-6

% n adalah C99, berfungsi tidak dengan VC ++.


2
%nada di C89. Itu tidak bekerja dengan MSVC karena Microsoft menonaktifkannya secara default untuk masalah keamanan; Anda harus menelepon _set_printf_count_outputdulu untuk mengaktifkannya. (Lihat jawaban Merlyn Morgan-Graham.)
jamesdlin

Tidak, C89 tidak mendefinisikan fitur ini / backdoorbug. Lihat K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? di mana URL-tagger untuk komentar ??
user411313

4
Anda salah besar. Itu tercantum dengan jelas di Tabel B-1 ( printfkonversi) dari Lampiran B dari K&R, edisi ke-2. (Halaman 244 salinan saya.) Atau lihat bagian 7.9.6.1 (halaman 134) dari spesifikasi ISO C90.
jamesdlin

Android juga menghapus specifier% n.
jww

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.