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
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 %nfait (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:FooBar
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.)
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É
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);
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.
+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.
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:
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);return0;}
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.
%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.)
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.
%n
rendprintf
accidentellement 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-sublanguagesRéponses:
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é.
Le code précédent s'imprime:
la source
int
est toujours signé.n format specifies disabled
. Quelle est la raison?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 uneint
variable), mais jusqu'à présent, personne n'a vraiment donné un exemple de son utilité . En voici une:imprimera:
avec Foo et Bar alignés. (C'est trivial de faire cela sans utiliser
%n
pour cet exemple particulier, et en général, on peut toujours interrompre ce premierprintf
appel: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.)la source
&n
est un pointeur (&
est l'opérateur d'adresse de); un pointeur est nécessaire car C est pass-by-value, et sans pointeur,printf
ne peut pas modifier la valeur den
. L'%*s
utilisation dans laprintf
chaîne de format imprime un%s
spécificateur (dans ce cas, la chaîne vide""
) en utilisant une largeur de champ den
caractères. Une explication desprintf
principes de base sort essentiellement du cadre de cette question (et réponse); Je vous recommande de lire laprintf
documentation ou de poser votre propre question sur SO.Je n'ai pas vraiment vu beaucoup d'utilisations pratiques du
%n
spé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
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
,%c
ou 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É
la source
%n
d'éviter d'utiliser une chaîne d'entrée non cochée comme chaîne deprintf
format.De là, nous voyons qu'il stocke le nombre de caractères imprimés jusqu'à présent.
Un exemple d'utilisation serait:
n_chars
aurait alors une valeur de12
.la source
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 avecsprintf
/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 avecsscanf
, d'autant plus que les fonctions de lascanf
famille 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.
la source
%n
, bien que je ne sois pas d'accord sur "toutes les réponses ...". = P%n
n'a absolument aucun danger de vulnérabilité pour un attaquant. L'infamie mal placée de%n
appartient 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%n
fait partie d'une chaîne de format intentionnelle utilisée.*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?L'argument associé à
%n
sera traité comme unint*
et est rempli avec le nombre total de caractères imprimés à ce point dans leprintf
.la source
L'autre jour, je me suis retrouvé dans une situation où
%n
cela 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:
la source
snprintf
lors de la vérification des valeurs de retour fonctionnent très bien carsnprintf
renvoie le nombre de caractères écrits. Peut - être quelque chose comme:int begin = snprintf(..., "blah blah %s %f yada yada", ...);
etint end = snprintf(..., "%s", ...);
puis la queue:snprintf(..., "blah blah");
.snprintf
appels multiples est que les substitutions peuvent être réorganisées dans d'autres locales, donc elles ne peuvent pas être interrompues comme ça.%n
; Je prétends que l'utilisation%n
est plus simple que les alternatives.Il n'imprime rien. Il est utilisé pour déterminer combien de caractères ont été imprimés avant d'
%n
apparaître dans la chaîne de format, et les afficher dans l'int fourni:( Documentation pour
_set_printf_count_output
)la source
Il stockera la valeur du nombre de caractères imprimés jusqu'à présent dans cette
printf()
fonction.Exemple:
La sortie de ce programme sera
la source
% n est C99, ne fonctionne pas avec VC ++.
la source
%n
existait 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_output
abord appeler pour l'activer. (Voir la réponse de Merlyn Morgan-Graham.)printf
conversions) 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._set_printf_count_output