27 votes

Quelle est la différence entre printf(s) et printf("%s", s)?

La question est simple et clair, s est une chaîne de caractères, j'ai soudain eu l'idée d'essayer d'utiliser printf(s) pour voir si elle fonctionne et j'ai reçu un avertissement dans un cas, et aucun dans l'autre.

char* s = "abcdefghij\n";
printf(s);

// Warning raised with gcc -std=c11: 
// format not a string literal and no format arguments [-Wformat-security]

// On the other hand, if I use 

char* s = "abc %d efg\n";
printf(s, 99);

// I get no warning whatsoever, why is that?

// Update, I've tested this:
char* s = "random %d string\n";
printf(s, 99, 50);

// Results: no warning, output "random 99 string".

Quel est donc le sous-jacent différence entre printf(s) et printf("%s", s) et pourquoi j'obtiens un message d'avertissement dans un seul cas?

25voto

Jonathan Leffler Points 299946

Dans le premier cas, la non-littéral de chaîne de format pourrait peut-être venir de code d'utilisateur ou fourni par l'utilisateur (run-time) de données, auquel cas elle peut contenir %s ou à d'autres spécifications de conversion, pour lequel vous n'avez pas transmis les données. Cela peut conduire à toutes sortes de problèmes de lecture (et d'écriture des problèmes si la chaîne contient %n - voir printf() ou votre bibliothèque C les pages de manuel).

Dans le second cas, le format de la chaîne de contrôle à la sortie et il n'a pas d'importance si une chaîne de caractères à imprimer contient des spécifications de conversion ou pas (bien que le code indiqué imprime un entier, pas une chaîne de caractères). Le compilateur (GCC ou Clang est utilisé dans la question) suppose que parce qu'il y a des arguments après le (non-littérale) chaîne de format, le programmeur sait ce qu'ils font.

La première est une " chaîne de format de la vulnérabilité. Vous pouvez effectuer une recherche pour plus d'informations sur le sujet.

GCC sait que la plupart du temps le seul argument printf() , avec une non-littéral de chaîne de format est une invitation à la difficulté. Vous pouvez utiliser puts() ou fputs() à la place. Il est suffisamment dangereux que GCC génère des avertissements avec le minimum de provocation.

Le problème plus général de non-littéral de chaîne de format peut aussi être problématique si vous n'êtes pas prudent - mais extrêmement utile en supposant que vous êtes prudent. Vous devez travailler dur pour obtenir de GCC à se plaindre: il faut à la fois -Wformat et -Wformat-nonliteral pour obtenir la plainte.

À partir des commentaires:

Donc, en ignorant l'avertissement, comme si je sais vraiment ce que je fais et il n'y aura pas d'erreurs, est l'un ou l'autre est plus efficace d'utiliser ou sont-ils les mêmes? En considérant à la fois l'espace et le temps.

De vos trois printf() des déclarations, étant donné les contraintes de contexte que la variable s est attribué immédiatement au-dessus de l'appel, il n'y a pas de problème réel. Mais vous pouvez utiliser puts(s) si vous avez omis le retour à la ligne à partir de la chaîne ou de l' fputs(s, stdout) comme il est et obtenir le même résultat, sans la surcharge d' printf() analyse de l'ensemble de la chaîne de constater qu'il est tout simple de caractères à imprimer.

Le deuxième printf() déclaration est également sans danger comme l'écrit; la chaîne de format correspond aux données transmises. Il n'y a pas de différence significative entre cela et un simple passage de la chaîne de format comme un littéral - sauf que le compilateur peut faire plus de vérifier si la chaîne de format est un littéral. Le résultat est le même.

Le tiers - printf() passe plus de données arguments que le format de la chaîne de besoins, mais c'est bénin. Ce n'est pas l'idéal, si. Encore une fois, le compilateur peut vérifier mieux si la chaîne de format est un littéral, mais la durée d'effet est pratiquement la même.

De la printf() spécification liée au dessus:

Chacune de ces fonctions, des convertis, des formats, et imprime ses arguments sous le contrôle du format. Le format est une chaîne de caractères, en commençant et se terminant dans son état de décalage initial, le cas échéant. Le format est composé de zéro, une ou plusieurs directives: les caractères ordinaires, qui sont simplement copiés sur le flux de sortie, et les spécifications de conversion, qui doit aboutir à la récupération de zéro ou plusieurs arguments. Les résultats ne sont pas définis si il n'existe pas suffisamment d'arguments pour le format. Si le format est épuisé alors que les arguments restent, l'excès d'arguments doit être évalué, mais ne sont pas ignorés.

Dans tous ces cas, il n'y a aucune indication de la chaîne de format n'est pas littérale. Cependant, une des raisons de vouloir un non-littéral de chaîne de format peut-être que, parfois, vous imprimez des nombres en virgule flottante en %f notation et, parfois, en %e de la notation, et vous devez choisir au moment de l'exécution. (Si c'est simplement basée sur la valeur, %g pourrait être approprié, mais il ya des moments où vous souhaitez que le contrôle explicite - toujours %e ou toujours %f.)

6voto

Sourav Ghosh Points 54713

L'avertissement dit tout.

Tout d'abord, pour discuter de la question, comme par la signature, le premier paramètre printf() est un format de chaîne de caractères qui peut contenir des spécificateurs de format (indicateur de conversion). Dans le cas d'une chaîne de caractères contient un spécificateur de format et de l'argument correspondant n'est pas fourni, il invoque un comportement indéfini.

Donc, un nettoyant (ou plus sûr) approche (de l'impression d'une chaîne qui n'a pas besoin de spécification de format) serait puts(s); sur printf(s); (le premier n'a pas de processus d' s pour la conversion des prescripteurs, en supprimant la raison de la possible UB dans le cas d'une version ultérieure). Vous pouvez choisir fputs(), si vous êtes inquiet au sujet de la fin de saut de ligne qui est automatiquement ajoutée en puts().


Cela dit, concernant l'option d'avertissement, -Wformat-security en ligne manuel de gcc

À l'heure actuelle, cela met en garde sur les appels d' printf et scanf fonctions où la chaîne de format n'est pas une chaîne littérale et il n'y a pas de format arguments, comme en printf (foo);. Cela peut être un trou de sécurité si la chaîne de format est venu de douteuses en entrée et contient %n.

Dans ton premier cas, il n'y a qu'un seul argument fourni à l' printf(), ce qui n'est pas une chaîne littérale, mais plutôt une variable, qui peut être très bien généré/ peuplées au moment de l'exécution, et si qui contient inattendu spécificateurs de format, il peut invoquer l'UB. Le compilateur n'a aucun moyen de vérifier la présence de tout spécificateur de format dans cette. C'est le problème de sécurité n'.

Dans le second cas, l'accompagnement argument est fourni, le spécificateur de format n'est pas le seul argument passé à l' printf(), de sorte que le premier argument n'a pas besoin d'être vérifiée. D'où l'avertissement n'est pas là.


Mise à jour:

Quant à la troisième, avec l'excès d' argument que requis par le format fourni chaîne

printf(s, 99, 50);

citant C11, au chapitre §7.21.6.1

[...] Si le format est épuisé alors que les arguments restent, l'excédent arguments sont évalué (comme toujours), mais ne sont pas ignorés. [...]

Donc, en passant excès argument n'est pas un problème (du point de vue du compilateur) et c'est bien définie. PAS de portée d'une quelconque attention.

5voto

Scott Mermelstein Points 7848

Il y a deux choses dans votre question.

Le premier est couvert de manière succincte par Jonathan Leffler - l'avertissement que vous êtes l'obtention est parce que la chaîne n'est pas littérale et n'ont pas de spécificateurs de format en elle.

L'autre est le mystère de pourquoi le compilateur n'a pas d'émettre un avertissement que le nombre de vos arguments ne correspond pas au nombre de prescripteurs. La réponse courte est "parce que ce n'est pas," mais plus précisément, printf est une variadic fonction. Il prend un nombre quelconque d'arguments après la première spécification de format - de 0 sur place. Le compilateur ne peut pas vérifier pour voir si vous avez donné la bonne quantité; c'est à la fonction printf lui-même, et conduit à un comportement indéfini que Joachim mentionné dans les commentaires.

EDIT: Je vais encore répondre à votre question, comme un moyen d'obtenir sur une petite caisse à savon.

Quelle est la différence entre printf(s) et printf("%s", s)? Simple - dans le dernier, vous êtes à l'aide de printf, comme il l'a déclaré. "%s" est un const char *, et il va par la suite de ne pas générer le message d'avertissement.

Dans vos commentaires à d'autres réponses, vous avez parlé de "en Ignorant l'avertissement...". Ne pas le faire. Avertissements existent pour une raison, et doivent être résolus (sinon, ils font juste du bruit, et vous allez manquer les avertissements qui ont vraiment de l'importance parmi les trucs de tous ceux qui n'en ont pas.)

Votre problème peut être résolu de plusieurs manières.

const char* s = "abcdefghij\n";
printf(s);

résoudre l'avertissement, parce que vous êtes maintenant à l'aide d'un pointeur const, et il y a aucun des dangers que Jonathan mentionné. (Vous pouvez également déclarer comme const char* const s, mais ne pas avoir à le faire. La première const est important, parce qu'il compare ensuite la déclaration d' printf, et parce qu' const char* s signifie que les caractères pointée par s ne peut pas changer, c'est à dire la chaîne de caractères est un littéral.)

Ou, encore plus simple, il suffit de faire:

printf("abcdefghij\n");

C'est implicitement un pointeur const, et pas non plus un problème.

2voto

Andreas Spindler Points 1612

La raison sous-jacente: printf est déclaré comme:

int printf(const char *fmt, ...) __attribute__ ((format(printf, 1, 2)));

Cela dit gcc printf est une fonction avec un printf de style de l'interface où la chaîne de format qui vient en premier. À mon humble avis il doit être littéral; je ne pense pas qu'il y a une façon de dire au bon compilateur s est en fait un pointeur vers une chaîne littérale, il avait vu avant.

Lire plus à propos de __attribute__ ici.

2voto

plugwash Points 795

Quel est donc le sous-jacent différence entre printf(s) et printf("%s", s)

"printf(s)" traitera s comme une chaîne de format. Si s contient les spécificateurs de format, puis printf va les interpréter et aller à la recherche pour les varargs. Depuis pas de varargs existent réellement ce sera probablement déclencher un comportement indéterminé.

Si un attaquant de contrôles "s" alors que cela est susceptible d'être un trou de sécurité.

printf("%s",s), il suffit d'imprimer ce qui est dans la chaîne.

et pourquoi ai-je un message d'avertissement dans un seul cas?

Les avertissements sont un équilibre entre la capture dangereux de la bêtise et de ne pas créer trop de bruit.

C programmeurs sont dans le habbit de l'aide de printf et divers printf comme des fonctions* en tant que générique de fonctions d'impression, même quand ils n'ont pas réellement besoin de mise en forme. Dans ce contexte, il est facile pour quelqu'un de faire l'erreur de l'écriture printf(s) sans penser à où s est venu. Car la mise en page est assez inutile, sans données de format de printf(s) a peu d'utilisation légitime.

printf(s,format,arguments), d'autre part, indique que le programmeur veut délibérément mise en forme à prendre place.

Afaict cet avertissement n'est pas activée par défaut en amont de gcc, mais certaines distributions sont de l'allumer dans le cadre de leurs efforts pour réduire les failles de sécurité.

* Standard C, comme les fonctions sprintf et fprintf et de fonctions dans des bibliothèques tierces.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X