A che serve l'identificatore di formato% n in C?


125

Qual è l'uso dell'identificatore di %nformato in C? Qualcuno potrebbe spiegare con un esempio?


25
Che fine ha fatto l'arte di leggere il bel manuale?
Jens,

8
Penso che la vera domanda sia: qual è il PUNTO di un'opzione come questa? perché qualcuno vorrebbe sapere il valore dei numeri di caratteri stampati e tanto meno scrivere quel valore direttamente in memoria. Era come se gli sviluppatori si annoiassero e decisero di introdurre un bug nel kernal
jia chen,

Ecco perché Bionic l'ha lasciato andare.
solidak,

1
In realtà è una domanda valida e probabilmente non risponderà ai buoni manuali; è stato scoperto che %nrende printfaccidentalmente Turing completo e puoi ad esempio implementare Brainfuck in esso, vedi github.com/HexHive/printbf e oilshell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer,

Risposte:


147

Niente di stampato. L'argomento deve essere un puntatore a un int firmato, in cui è memorizzato il numero di caratteri scritti finora.

#include <stdio.h>

int main()
{
  int val;

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

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

  return 0;

}

Il codice precedente stampa:

blah  blah
val = 5

1
Dici che l'argomento deve essere un puntatore a un int con segno, quindi hai usato un int senza segno nel tuo esempio (probabilmente solo un refuso).
BTA

1
@AndrewS: poiché la funzione modificherà il valore della variabile.
Jack,

3
@Jack intè sempre firmato.
jamesdlin,

1
@jamesdlin: il mio errore. Mi dispiace .. non sapevo dove l'ho letto.
Jack,

1
Per qualche motivo l'esempio genera un errore con la nota n format specifies disabled. Per quale motivo?
Johnny_D,

186

La maggior parte di queste risposte spiega cosa %n fa (che non è stampare nulla e scrivere il numero di caratteri stampati finora su una intvariabile), ma finora nessuno ha davvero dato un esempio di quale uso abbia. Eccone uno:

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

stamperà:

hello: Foo
       Bar

con Foo e Bar allineati. (È banale farlo senza usare %nquesto esempio particolare, e in generale si potrebbe sempre interrompere quella prima printfchiamata:

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

Se la convenienza leggermente aggiunta valga la pena usare qualcosa di esoterico %n(e possibilmente introdurre errori) è aperto al dibattito.


3
Oh mio Dio - questa è una versione basata sui caratteri per calcolare la dimensione in pixel della stringa in un determinato carattere!

Potresti spiegare perché sono necessari & n e * s. Sono entrambi puntatori?
Andrew S,

9
@AndrewS &nè un puntatore ( &è l'operatore dell'indirizzo); un puntatore è necessario perché C è pass-by-value e senza un puntatore printfnon può modificare il valore di n. L' %*sutilizzo nella printfstringa di formato stampa un %sidentificatore (in questo caso la stringa vuota "") utilizzando una larghezza del campo di ncaratteri. Una spiegazione dei printfprincipi di base è sostanzialmente al di fuori dell'ambito di questa domanda (e risposta); Consiglio di leggere la printfdocumentazione o di porre una domanda separata su SO.
Jamesdlin,

3
Grazie per aver mostrato un caso d'uso. Non capisco perché la gente semplicemente copia e incolla il manuale in SO e lo riformula a volte. Siamo umani e tutto è fatto per una ragione che dovrebbe sempre essere spiegata in una risposta. "Non fa nulla" è come dire "La parola cool significa cool" - conoscenza quasi inutile.
the_endian il

1
@PSkocik È abbastanza contorto e soggetto a errori senza aggiungere un ulteriore livello di riferimento indiretto.
jamesdlin,

18

Non ho mai visto molti usi pratici del mondo reale dello %nspecificatore, ma ricordo che è stato usato nelle vulnerabilità di printf oldschool con un attacco di stringa di formato un po 'di tempo fa.

Qualcosa che è andato così

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

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

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

dove un utente malintenzionato potrebbe trarre vantaggio dal parametro username che viene passato a printf come stringa di formato e utilizzare una combinazione di %d, %co w / e per passare attraverso lo stack di chiamate e quindi modificare la variabile autorizzata su un valore vero.

Sì, è un uso esoterico, ma è sempre utile sapere quando si scrive un demone per evitare falle nella sicurezza? : D


1
Esistono più motivi che %nevitare di utilizzare una stringa di input non selezionata come stringa di printfformato.
Keith Thompson,

13

Da qui vediamo che memorizza il numero di caratteri stampati finora.

n L'argomento deve essere un puntatore a un numero intero in cui è scritto il numero di byte scritti finora sull'output da questa chiamata a una delle fprintf()funzioni. Nessun argomento viene convertito.

Un esempio di utilizzo sarebbe:

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

n_charsavrebbe quindi un valore di 12.


10

Finora tutte le risposte riguardano questo %n, ma non il motivo per cui qualcuno lo vorrebbe in primo luogo. Trovo che sia in qualche modo utile con sprintf/ snprintf, quando potrebbe essere necessario spezzare o modificare in seguito la stringa risultante, poiché il valore memorizzato è un indice di matrice nella stringa risultante. Questa applicazione è molto più utile, tuttavia, con sscanf, soprattutto perché le funzioni inscanf famiglia non restituiscono il numero di caratteri elaborati ma il numero di campi.

Un altro uso davvero hacker è ottenere gratuitamente uno pseudo-log10 mentre si stampa un numero come parte di un'altra operazione.


+1 per menzionare gli usi per %n, anche se mi permetto di dissentire su "tutte le risposte ...". = P
jamesdlin

1
I cattivi ragazzi ti ringraziano per l'uso di printf /% n, sprintf e sscanf;)
jww

6
@noloader: in che modo? L'uso di %n ha assolutamente zero pericolo di vulnerabilità a un utente malintenzionato. L'infamia mal riposta di %nappartiene davvero alla stupida pratica di passare una stringa di messaggio piuttosto che una stringa di formato come argomento del formato. Questa situazione ovviamente non si presenta mai quando %nfa effettivamente parte di una stringa di formato intenzionale utilizzata.
R .. GitHub smette di aiutare ICE il

% n ti consente di scrivere in memoria. Penso che tu stia supponendo che l'attaccante non controlli quel puntatore (potrei sbagliarmi). Se l'attaccante controlla il puntatore (è solo un altro parametro di printf), potrebbe eseguire una scrittura di 4 byte. Se lui / lei può trarre profitto è una storia diversa.
jww

8
@noloader: è vero per qualsiasi uso di puntatori. Nessuno dice "Grazie, ragazzi cattivi" per aver scritto *p = f();. Perché %n, che è solo un altro modo di scrivere un risultato sull'oggetto puntato da un puntatore, dovrebbe essere considerato "pericoloso", piuttosto che considerare il puntatore stesso pericoloso?
R .. GitHub smette di aiutare ICE il

10

L'argomento associato al %nsarà trattato come un int*ed è riempito con il numero di caratteri totali stampati in quel punto nel printf.


9

L'altro giorno mi sono trovato in una situazione in cui %navrei risolto il mio problema. A differenza della mia precedente risposta , in questo caso, non riesco a escogitare una buona alternativa.

Ho un controllo GUI che visualizza del testo specificato. Questo controllo può visualizzare parte di quel testo in grassetto (o in corsivo, sottolineato, ecc.) E posso specificare quale parte specificando gli indici di carattere iniziale e finale.

Nel mio caso, sto generando il testo con il controllo snprintfe vorrei che una delle sostituzioni venisse messa in grassetto. Trovare gli indici iniziale e finale di questa sostituzione non è banale perché:

  • La stringa contiene più sostituzioni e una delle sostituzioni è un testo arbitrario, specificato dall'utente. Ciò significa che fare una ricerca testuale della sostituzione a cui tengo è potenzialmente ambiguo.

  • La stringa di formato potrebbe essere localizzata e potrebbe utilizzare l' $estensione POSIX per gli identificatori di formato posizionali. Pertanto, la ricerca della stringa di formato originale per gli identificatori di formato stessi non è banale.

  • L'aspetto della localizzazione significa anche che non riesco facilmente a suddividere la stringa di formato in più chiamate a snprintf.

Pertanto, il modo più semplice per trovare gli indici attorno a una particolare sostituzione sarebbe fare:

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);

Ti darò +1 per il caso d'uso. Ma fallirai un controllo, quindi dovresti probabilmente escogitare un altro modo per segnare l'inizio e la fine del testo in grassetto. Sembra che tre snprintfdurante il controllo dei valori di ritorno funzionino bene poiché snprintfrestituisce il numero di caratteri scritti. Forse qualcosa di simile: int begin = snprintf(..., "blah blah %s %f yada yada", ...);e int end = snprintf(..., "%s", ...);poi la coda: snprintf(..., "blah blah");.
jww

3
@jww Il problema con più snprintfchiamate è che le sostituzioni potrebbero essere riorganizzate in altri locali, quindi non possono essere suddivise in questo modo.
jamesdlin,

Grazie per l'esempio Ma non potresti scrivere una sequenza di controllo terminale per rendere l'output in grassetto proprio prima del campo e poi scrivere una sequenza dopo di esso? Se non si codificano le sequenze di controllo del terminale, è possibile renderle anche posizionali (riordinabili).
PSkocik,

1
@PSkocik Se stai inviando a un terminale. Se stai lavorando con, per esempio, un controllo rich-edit di Win32, questo non ti aiuterà se non vuoi tornare indietro e analizzare le sequenze di controllo del terminale in seguito. Ciò presuppone anche che si desideri onorare le sequenze di controllo terminale nel resto del testo sostituito; se non lo fai, allora dovresti filtrare o sfuggire a quelli. Non sto dicendo che è impossibile farne a meno %n; Sto sostenendo che l'utilizzo %nè più semplice delle alternative.
jamesdlin,

1

Non stampa nulla. Viene utilizzato per capire quanti caratteri sono stati stampati prima che %nappaiano nella stringa di formato e li restituiscono all'int fornito:

#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;
}

( Documentazione per_set_printf_count_output )


0

Memorizzerà il valore del numero di caratteri stampati finora in quella printf()funzione.

Esempio:

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

L'output di questo programma sarà

Hello World
Characters printed so far = 12

quando provo il tuo codice mi dà: Ciao personaggi del mondo stampati finora = 36 ,,,,, perché 36 ?! Uso un GCC a 32 bit in una macchina Windows.
Sina Karvandi,

-6

% n è C99, non funziona con VC ++.


2
%nesisteva nel C89. Non funziona con MSVC perché Microsoft lo disabilita di default per motivi di sicurezza; devi _set_printf_count_outputprima chiamare per abilitarlo. (Vedi la risposta di Merlyn Morgan-Graham.)
jamesdlin il

No, C89 non definisce questa funzione / backdoorbug. Vedi K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? dove si trova l'URL-tagger per i commenti ??
user411313

4
Ti sbagli semplicemente. È elencato chiaramente nella Tabella B-1 ( printfconversioni) dell'Appendice B di K&R, 2a edizione. (Pagina 244 della mia copia.) Oppure vedere la sezione 7.9.6.1 (pagina 134) della specifica ISO C90.
jamesdlin,

Android ha anche rimosso l'identificatore% n.
jww

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.