2010-08-26 2 views
26

Tout programmeur C qui a travaillé pendant plus d'une semaine a rencontré des accidents qui résultent de l'appel printf avec spécificateurs plus de format que les arguments réels, par exemple:Passant trop d'arguments à printf

printf("Gonna %s and %s, %s!", "crash", "burn"); 

Cependant, sont-il mauvaises choses similaires qui peuvent se produire lorsque vous passez trop nombreux arguments à printf?

printf("Gonna %s and %s!", "crash", "burn", "dude"); 

Ma connaissance de l'ensemble x86/x64 me porte à croire que cela est sans danger, bien que je ne suis pas convaincu qu'il n'y a pas une condition de bord me manque, et je n'ai aucune idée sur d'autres architectures. Cette condition est-elle garantie pour être inoffensive, ou y a-t-il un piège potentiellement générateur d'accidents ici aussi?

+3

Pas une réponse à votre question, ce Stacker est correct, mais pour les accidents. 'gcc' devrait donner de bons avertissements à ce sujet, donc il n'y a vraiment aucune excuse pour négliger celui-là ;-) –

+0

Comment GCC pourrait-il donner des avertissements pour cela? Considérez que la chaîne de format ne doit pas nécessairement être une chaîne constante. Ca peut être n'importe quel 'char *' –

+1

GCC peut donner de bons avertissements quand il peut connaître la chaîne de format au moment de la compilation. Comme cela représente une grande partie des cas d'utilisation rationnelle de 'printf' et de ses amis, ces avertissements sont précieux et devraient être pris en compte. – RBerteig

Répondre

12

Vous savez sans doute le prototype de la fonction printf comme quelque chose comme ça

int printf(const char *format, ...); 

Une version plus complète de ce serait en fait

int __cdecl printf(const char *format, ...); 

Le __cdecl définit la « convention d'appel » qui, avec d'autres choses, décrit comment les arguments sont traités. Dans le cas présent, cela signifie que les arguments sont poussés sur la pile et que la pile est nettoyée par la fonction effectuant l'appel. Une alternative à _cdecl est __stdcall, il y a d'autres. Avec __stdcall la convention est que les arguments sont poussés sur la pile et nettoyés par la fonction appelée. Cependant, autant que je sache, il n'est pas possible pour une fonction __stdcall d'accepter un nombre variable d'arguments. Cela a du sens car il ne sait pas combien de pile à nettoyer. Le long et le court de cela est que dans le cas de __cdecl fonctionne son sûr de passer cependant de nombreux args que vous voulez, puisque le nettoyage est effectué dans le code faisant l'appel. Si vous deviez en quelque sorte passer trop d'arguments à une fonction __stdcall, cela entraînerait une corruption de la pile. Un exemple d'où cela pourrait arriver est si vous aviez le mauvais prototype.

Plus d'informations sur les conventions d'appel peuvent être trouvées sur Wikipedia here.

+6

__cdecl est un Win32ism, créé par le fait que certains anciens compilateurs DOS supportaient les conventions d'appel C et pascal. – ninjalj

+1

@ninjalj, '__cdecl' est uniquement pris en charge par les compilateurs MS, mais la remarque générale sur les conventions d'appel est valable pour tous les systèmes d'exploitation. –

+0

@JSBangs: __cdecl a également été pris en charge par les compilateurs Borland IIRC. De plus, sur la plupart des autres OS, C utilise uniquement la convention d'appel C (droite à gauche, l'appelant nettoie la pile), éventuellement avec des variantes pour ISR, compatibilité avec d'autres compilateurs et/ou sauvegarde des args sur registres (regparm de GCC). AFAIK, Win32 est la seule plate-forme où vous pouvez sélectionner une convention d'appel qui ne supporte pas les fonctions vararg. – ninjalj

3

Tous les arguments seront poussés sur la pile et supprimés si le cadre de la pile est supprimé. Ce comportement est indépendant d'un processeur spécifique. (Je me souviens seulement d'un mainframe qui n'avait pas de pile, conçu dans les années 70) Donc, oui, le deuxième exemple n'échouera pas.

3

printf est conçu pour accepter n'importe quel nombre d'arguments. printf lit ensuite le spécificateur de format (premier argument) et extrait les arguments de la liste d'arguments si nécessaire. C'est pourquoi trop peu d'arguments se plantent: le code commence simplement à utiliser des arguments inexistants, à accéder à de la mémoire qui n'existe pas, ou à quelque chose de mauvais. Mais avec trop d'arguments, les arguments supplémentaires seront simplement ignorés. Le spécificateur de format utilisera moins d'arguments que ceux qui ont été transmis dans

+0

Pour ajouter à cela, votre compilateur peut également éliminer les paramètres supplémentaires s'il peut détecter qu'ils sont inutilisés. Vous devriez regarder la sortie de l'assemblage pour savoir si les paramètres supplémentaires sont réellement passés à 'printf' ou s'ils sont optimisés. – bta

+0

Si le compilateur sait avec certitude qu'il appelle quelque chose qui utilise le langage de format de printf (et que GCC a un attribut pour décorer ses propres fonctions printf), il est en principe sûr de faire cette optimisation. Il devrait toujours agir comme s'il avait calculé tous les paramètres au cas où l'un des éléments non utilisés aurait des effets secondaires. – RBerteig

27

Online C Draft Standard (n1256), section 7.19.6.1, paragraphe 2:.

La fonction fprintf écrit la sortie dans le flux pointé par stream, sous le contrôle de la chaîne pointé par format qui spécifie comment les arguments suivants sont convertis pour la sortie.S'il n'y a pas suffisamment d'arguments pour le format, le comportement est indéfini. Si le format est épuisé alors que les arguments restent, les arguments en excès sont évalués (comme toujours) mais ignorés autrement. La fonction fprintf renvoie lorsque la fin de la chaîne de format est rencontrée.

Le comportement pour toutes les autres fonctions *printf() est le même que pour les arguments en excès, sauf pour vprintf() (évidemment).

+0

Cela s'applique-t-il aux fonctions variées écrites par l'utilisateur? – immibis

+0

@immibis: La principale raison pour laquelle ce n'est pas le cas, c'est que certaines conventions d'appel ont les arguments pop callee de la pile au retour. Cette règle exige essentiellement que l'implémentation prenne en charge le cas général, ou prenne en charge un grand nombre de fonctions spéciales pour 'printf'. La plupart des systèmes avec une convention d'appel callee-pops ne l'utilisent pas pour des fonctions variées, pour cette raison. (Et aussi que l'instruction 'ret imm16' de x86 requiert le nombre d'octets à pop pour être une constante de compilation). 'printf' est à peu près le pire des cas pour la capacité de l'appelé à détecter le nombre d'arguments: polymorphe, pas de sentinelle –

+1

Cela devrait être la réponse acceptée! Qui se soucie d'appeler des conventions ici? C'est juste un détail pour la mise en œuvre de la norme. – Xlea

-2

Commentaire: les deux avertissements gcc et produisent clang:

$ clang main.c 
main.c:4:29: warning: more '%' conversions than data arguments [-Wformat] 
    printf("Gonna %s and %s, %s!", "crash", "burn"); 
          ~^ 
main.c:5:47: warning: data argument not used by format string 
         [-Wformat-extra-args] 
    printf("Gonna %s and %s!", "crash", "burn", "dude"); 
     ~~~~~~~~~~~~~~~~~~     ^
2 warnings generated. 
+0

Le problème est principalement lorsque vous générez votre propre chaîne de format, qui n'est pas connue au moment de la compilation. – meneldal