À quoi sert le spécificateur de format% n en C?

125

À quoi sert le %nspécificateur de format en C? Quelqu'un pourrait-il expliquer avec un exemple?

Josh
la source
25
Qu'est devenu l'art raffiné de lire le beau manuel?
Jens
8
Je pense que la vraie question est quel est le POINT d'une option comme celle-ci? pourquoi quelqu'un voudrait-il connaître la valeur des nombres de caractères imprimés et encore moins écrire cette valeur directement dans la mémoire. C'était comme si les développeurs s'ennuyaient et décidaient d'introduire un bogue dans le noyau
jia chen
C'est pourquoi Bionic l'a abandonné.
solidak
1
C'est en fait une question valable, à laquelle les bons manuels ne répondront probablement pas; il a été découvert qui %nrend printfaccidentellement Turing-complet et vous pouvez par exemple implémenter Brainfuck dedans, voir github.com/HexHive/printbf et oilhell.org/blog/2019/02/07.html#appendix-a-minor-sublanguages
John Frazer

Réponses:

147

Rien d'imprimé. L'argument doit être un pointeur vers un entier signé, où le nombre de caractères écrits jusqu'à présent est stocké.

#include <stdio.h>

int main()
{
  int val;

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

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

  return 0;

}

Le code précédent s'imprime:

blah  blah
val = 5
Touche étoile
la source
1
Vous mentionnez que l'argument doit être un pointeur vers un int signé, puis vous avez utilisé un int non signé dans votre exemple (probablement juste une faute de frappe).
bta
1
@AndrewS: Parce que la fonction modifiera la valeur de la variable.
Jack
3
@Jack intest toujours signé.
jamesdlin
1
@jamesdlin: Mon erreur. Je suis désolé ... je ne savais pas où j'avais lu ça.
Jack
1
Pour une raison quelconque, l'exemple soulève une erreur avec une note n format specifies disabled. Quelle est la raison?
Johnny_D
186

La plupart de ces réponses expliquent ce que %n fait (qui est de ne rien imprimer et d'écrire le nombre de caractères imprimés jusqu'à présent dans une intvariable), mais jusqu'à présent, personne n'a vraiment donné un exemple de son utilité . En voici une:

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

imprimera:

hello: Foo
       Bar

avec Foo et Bar alignés. (C'est trivial de faire cela sans utiliser %npour cet exemple particulier, et en général, on peut toujours interrompre ce premier printfappel:

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

La question de savoir si la commodité légèrement ajoutée vaut la peine d'utiliser quelque chose d'ésotérique comme %n(et éventuellement d'introduire des erreurs) est ouverte à débat.)

Jamesdlin
la source
3
Oh mon Dieu - c'est une version basée sur les caractères du calcul de la taille en pixels d'une chaîne dans une police donnée!
Pourriez-vous expliquer pourquoi & n et * s sont nécessaires. Sont-ils tous les deux des pointeurs?
Andrew S
9
@AndrewS &nest un pointeur ( &est l'opérateur d'adresse de); un pointeur est nécessaire car C est pass-by-value, et sans pointeur, printfne peut pas modifier la valeur de n. L' %*sutilisation dans la printfchaîne de format imprime un %sspécificateur (dans ce cas, la chaîne vide "") en utilisant une largeur de champ de ncaractères. Une explication des printfprincipes de base sort essentiellement du cadre de cette question (et réponse); Je vous recommande de lire la printfdocumentation ou de poser votre propre question sur SO.
jamesdlin
3
Merci d'avoir montré un cas d'utilisation. Je ne comprends pas pourquoi les gens se contentent de copier-coller le manuel dans SO et de le reformuler parfois. Nous sommes des humains et tout est fait pour une raison qui doit toujours être expliquée dans une réponse. «Ne fait rien», c'est comme dire «Le mot cool signifie cool» - une connaissance presque inutile.
the_endian
1
@PSkocik C'est assez compliqué et sujet aux erreurs sans ajouter un niveau supplémentaire d'indirection.
jamesdlin
18

Je n'ai pas vraiment vu beaucoup d'utilisations pratiques du %nspécificateur dans le monde réel , mais je me souviens qu'il a été utilisé dans les vulnérabilités printf oldschool avec une attaque de chaîne de format il y a quelque temps.

Quelque chose qui est allé comme ça

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

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

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

où un utilisateur malveillant pourrait profiter du paramètre de nom d'utilisateur qui est passé dans printf comme chaîne de format et utiliser une combinaison de %d, %cou w / e pour parcourir la pile d'appels, puis modifier la variable autorisée à une valeur vraie.

Ouais c'est une utilisation ésotérique, mais toujours utile à savoir lors de l'écriture d'un démon pour éviter les failles de sécurité? :RÉ

Xzhsh
la source
1
Il y a plus de raisons que %nd'éviter d'utiliser une chaîne d'entrée non cochée comme chaîne de printfformat.
Keith Thompson
13

De là, nous voyons qu'il stocke le nombre de caractères imprimés jusqu'à présent.

n L'argument doit être un pointeur vers un entier dans lequel est écrit le nombre d'octets écrits jusqu'à présent dans la sortie par cet appel à l'une des fprintf()fonctions. Aucun argument n'est converti.

Un exemple d'utilisation serait:

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

n_charsaurait alors une valeur de 12.

KLee1
la source
10

Jusqu'à présent, toutes les réponses sont à ce sujet %n, mais pas pourquoi quiconque le voudrait en premier lieu. Je trouve que c'est quelque peu utile avec sprintf/ snprintf, lorsque vous devrez peut-être rompre ou modifier plus tard la chaîne résultante, car la valeur stockée est un index de tableau dans la chaîne résultante. Cette application est cependant beaucoup plus utile avec sscanf, d'autant plus que les fonctions de la scanffamille ne renvoient pas le nombre de caractères traités mais le nombre de champs.

Une autre utilisation vraiment hackeuse est d'obtenir un pseudo-log10 gratuitement en même temps tout en imprimant un nombre dans le cadre d'une autre opération.

R .. GitHub STOP AIDING ICE
la source
+1 pour mentionner les utilisations de %n, bien que je ne sois pas d'accord sur "toutes les réponses ...". = P
jamesdlin
1
Les méchants merci pour votre utilisation de printf /% n, sprintf et sscanf;)
jww
6
@noloader: Comment ça? L'utilisation de %n n'a absolument aucun danger de vulnérabilité pour un attaquant. L'infamie mal placée de %nappartient vraiment à la pratique stupide de passer une chaîne de message plutôt qu'une chaîne de format comme argument de format. Cette situation ne se produit bien sûr jamais lorsque %nfait partie d'une chaîne de format intentionnelle utilisée.
R .. GitHub STOP HELPING ICE
% n vous permet d'écrire dans la mémoire. Je pense que vous supposez que l'attaquant ne contrôle pas ce pointeur (je peux me tromper). Si l'attaquant contrôle le pointeur (c'est juste un autre paramètre à printf), il pourrait effectuer une écriture de 4 octets. Qu'il / elle puisse en profiter est une autre histoire.
jww
8
@noloader: C'est vrai pour toute utilisation de pointeurs. Personne ne dit «merci aux méchants» d'avoir écrit *p = f();. Pourquoi %n, qui n'est qu'une autre façon d'écrire un résultat sur l'objet pointé par un pointeur, devrait-il être considéré comme «dangereux», plutôt que de considérer le pointeur lui-même dangereux?
R .. GitHub STOP HELPING ICE
10

L'argument associé à %nsera traité comme un int*et est rempli avec le nombre total de caractères imprimés à ce point dans le printf.

Evan
la source
9

L'autre jour, je me suis retrouvé dans une situation où %ncela résoudrait bien mon problème. Contrairement à ma réponse précédente , dans ce cas, je ne peux pas concevoir une bonne alternative.

J'ai un contrôle GUI qui affiche du texte spécifié. Ce contrôle peut afficher une partie de ce texte en gras (ou en italique, ou souligné, etc.), et je peux spécifier quelle partie en spécifiant les index des caractères de début et de fin.

Dans mon cas, je génère le texte vers le contrôle avec snprintf, et j'aimerais que l'une des substitutions soit en gras. Trouver les indices de début et de fin de cette substitution n'est pas trivial car:

  • La chaîne contient plusieurs substitutions et l'une des substitutions est un texte arbitraire spécifié par l'utilisateur. Cela signifie que faire une recherche textuelle pour la substitution qui me tient à cœur est potentiellement ambiguë.

  • La chaîne de format peut être localisée et utiliser l' $extension POSIX pour les spécificateurs de format positionnels. Par conséquent, rechercher dans la chaîne de format d'origine les spécificateurs de format eux-mêmes n'est pas trivial.

  • L'aspect de localisation signifie également que je ne peux pas facilement diviser la chaîne de format en plusieurs appels à snprintf.

Par conséquent, le moyen le plus simple de trouver les indices autour d'une substitution particulière serait de faire:

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);
Jamesdlin
la source
Je vous donnerai +1 pour le cas d'utilisation. Mais vous allez échouer à un audit, vous devriez donc probablement trouver une autre façon de marquer le début et la fin du texte en gras. Il semble que trois snprintflors de la vérification des valeurs de retour fonctionnent très bien car snprintfrenvoie le nombre de caractères écrits. Peut - être quelque chose comme: int begin = snprintf(..., "blah blah %s %f yada yada", ...);et int end = snprintf(..., "%s", ...);puis la queue: snprintf(..., "blah blah");.
jww
3
@jww Le problème avec les snprintfappels multiples est que les substitutions peuvent être réorganisées dans d'autres locales, donc elles ne peuvent pas être interrompues comme ça.
jamesdlin
Merci pour l'exemple. Mais ne pourriez-vous pas, par exemple, écrire une séquence de contrôle de terminal pour rendre la sortie en gras juste avant le champ, puis écrire une séquence après? Si vous ne codez pas en dur les séquences de contrôle du terminal, vous pouvez également les rendre positionnelles (réorganisables).
PSkocik
1
@PSkocik Si vous sortez vers un terminal. Si vous travaillez avec, par exemple, un contrôle d'édition enrichi Win32, cela n'aidera pas à moins que vous ne souhaitiez revenir en arrière et analyser les séquences de contrôle du terminal par la suite. Cela suppose également que vous souhaitiez respecter les séquences de contrôle du terminal dans le reste du texte substitué; si vous ne le faites pas, vous devrez les filtrer ou les échapper. Je ne dis pas qu'il est impossible de s'en passer %n; Je prétends que l'utilisation %nest plus simple que les alternatives.
jamesdlin
1

Il n'imprime rien. Il est utilisé pour déterminer combien de caractères ont été imprimés avant d' %napparaître dans la chaîne de format, et les afficher dans l'int fourni:

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

( Documentation pour_set_printf_count_output )

Merlyn Morgan-Graham
la source
0

Il stockera la valeur du nombre de caractères imprimés jusqu'à présent dans cette printf()fonction.

Exemple:

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

La sortie de ce programme sera

Hello World
Characters printed so far = 12
Sudhanshu Mishra
la source
quand j'essaye de coder ça me donne: Hello World Caractères imprimés jusqu'à présent = 36 ,,,,, pourquoi 36?! J'utilise un GCC 32 bits dans une machine Windows.
Sina Karvandi
-6

% n est C99, ne fonctionne pas avec VC ++.

utilisateur411313
la source
2
%nexistait en C89. Il ne fonctionne pas avec MSVC car Microsoft l'a désactivé par défaut pour des raisons de sécurité; vous devez d' _set_printf_count_outputabord appeler pour l'activer. (Voir la réponse de Merlyn Morgan-Graham.)
jamesdlin
Non, C89 ne définit pas cette fonctionnalité / backdoorbug. Voir K & R + ANSI-C amazon.com/Programming-Language-2nd-Brian-Kernighan/dp/… ?? où est l'URL-tagger pour les commentaires ??
user411313
4
Vous avez tout simplement tort. Il est clairement répertorié dans le tableau B-1 ( printfconversions) de l'annexe B de K&R, 2e édition. (Page 244 de mon exemplaire.) Ou voir la section 7.9.6.1 (page 134) de la spécification ISO C90.
jamesdlin
Android a également supprimé le spécificateur% n.
jww