printf () sans argument en C comstack bien. Comment?

J’ai essayé le programme ci-dessous et je m’attendais à avoir une erreur de compilation, mais pourquoi le compilateur ne donne aucune erreur?

#include  int main(void) { printf("%d\n"); return 0; } 

Pourquoi la sortie dépend-elle du compilateur? Voici la sortie sur différents compilateurs

Sortie sur IDE Orwell Dev C ++ (utilise gcc 4.8.1): 0

Sortie sur Visual C ++ fournie par Visual Studio 2010: 0

CodeBlocks IDE (utilise gcc 4.7.1): valeur de garbage

Compilateur en ligne ideone.com: garbage value

Qu’est-ce qui ne va pas ici ?

Votre programme se comstackra bien, car printf() est une fonction variadique et la vérification de la correspondance du nombre de spécificateurs de format avec l’argument fourni n’est pas effectuée par défaut.

Au moment de l’exécution, votre programme présente un comportement non défini , car aucun argument fourni ne doit être imprimé à l’aide du spécificateur de format fourni.

Selon le chapitre 7.19.6.1, norme c99 , (à partir de fprintf() )

S’il n’y a pas suffisamment d’arguments pour le format, le comportement est indéfini.

Si vous comstackz à l’aide de l’indicateur -Wformat dans gcc , votre compilateur -Wformat l’avertissement relatif à l’incompatibilité.

En raison de la façon dont fonctionnent les arguments variadiques C, le compilateur ne peut pas suivre leur utilisation correcte. Il est toujours légal (syntaxiquement) de fournir moins, ou plus, de parameters nécessaires au bon fonctionnement de la fonction, bien que cela aboutisse généralement à un comportement indéfini lorsque vous regardez la norme.

La déclaration de printf ressemble à ceci:

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

Le compilateur ne voit que ... et sait qu’il peut y avoir zéro ou plusieurs arguments supplémentaires que la fonction peut utiliser ou non. La fonction appelée ne sait pas combien d’arguments sont passés; au mieux, il peut supposer que toutes les informations nécessaires ont été transmises et rien de plus.

Comparez cela avec d’autres langages, comme C #:

 void WriteLine(ssortingng format, params object[] arguments); 

Ici, la méthode sait exactement combien d’arguments supplémentaires ont été passés (en faisant des arguments.Length ).

En C, les fonctions variadiques et en particulier printf sont une cause fréquente de vulnérabilités de sécurité. Printf finit par lire les octets bruts de la stack, ce qui peut Printf des détails importants sur votre application et son environnement de sécurité.

Pour cette raison, Clang et GCC prennent en charge une extension spéciale permettant de valider les formats printf . Si vous utilisez une chaîne de format non valide, vous recevrez un avertissement (pas une erreur).

 code.c:4:11: warning: more '%' conversions than data arguments [-Wformat] printf("%d\n"); ~~^ 

Ceci est simplement un comportement indéfini si vous ne fournissez pas suffisamment d’arguments pour printf , ce qui signifie que le comportement est imprévisible. Tiré du projet de norme C99, section 7.19.6.1 la fonction 7.19.6.1 qui couvre également printf dans ce cas:

S’il n’y a pas suffisamment d’arguments pour le format, le comportement est indéfini.

Comme printf est une fonction variadique, il n’existe pas de correspondance des arguments avec la déclaration de fonction. Le compilateur doit donc prendre en charge la vérification de format de chaîne, qui est couverte par l’ indicateur -Wformat dans gcc :

Vérifiez les appels à printf et scanf, etc., pour vous assurer que les arguments fournis ont des types appropriés à la chaîne de format spécifiée et que les conversions spécifiées dans la chaîne de format ont un sens. Cela inclut les fonctions standard, et d’autres spécifiées par les atsortingbuts de format (voir Atsortingbuts de fonction), […]

L’activation d’un nombre suffisant d’avertissements du compilateur est importante. Pour ce code, gcc utilisant l’indicateur -Wall nous indique ( voyez-le en direct ):

  warning: format '%d' expects a matching 'int' argument [-Wformat=] printf("%d\n"); ^ 

Cela comstack bien. Parce qu’il correspond au prototype printf () qui est

 printf(const char *,...); 

Pendant l’exécution, l’appel

 printf("%d\n"); 

Essaie d’extraire la valeur du deuxième argument et, puisque vous n’avez rien transmis, elle peut obtenir une valeur erronée et l’afficher. Le comportement n’est donc pas défini ici.

Vous invoquez un comportement indéfini. C’est votre problème, pas celui du compilateur, et fondamentalement tout est “autorisé” à se produire.

Bien entendu, pratiquement tous les compilateurs existants devraient pouvoir vous avertir de cette condition particulière concernant printf() , il vous suffit de le leur permettre (en activant et en tenant compte des avertissements du compilateur).

En général, les messages de diagnostic (vous les considérez peut-être comme des erreurs de compilation) ne sont pas garantis pour les comportements non définis (comme le manque d’arguments pour l’appel de la fonction printf , comme dans votre cas), qui ne sont pas comptabilisés comme violation de règle de syntaxe ou de contrainte.

C11 (N1570) §5.1.1.3 / p1 Diagnostic :

Une implémentation conforme doit produire au moins un message de diagnostic (identifié de manière définie par l’implémentation) si une unité de traduction en cours de prétraitement ou une unité de traduction contient une violation d’ une règle ou contrainte de syntaxe , même si le comportement est également spécifié explicitement comme indéfini ou implémentation. défini. Les messages de diagnostic ne doivent pas nécessairement être produits dans d’autres circonstances. 9)

En d’autres termes, votre compilateur est libre de traduire une telle unité, mais vous ne devez jamais l’exécuter ni compter sur son comportement (car il est de fait imprévisible). De plus, votre compilateur est autorisé à ne fournir aucune documentation (C Standard ne l’exige pas pour le faire), comme c’est le cas pour les comportements définis par l’implémentation ou spécifiques à l’environnement local.

C11 (N1570) §3.4.3 / p2 comportement non défini :

NOTE Un comportement indéfini possible peut aller d’ignorer complètement la situation avec des résultats imprévisibles, de se comporter pendant la traduction ou l’exécution du programme d’une manière documentée caractéristique de l’environnement (avec ou sans émission d’un message de diagnostic), jusqu’à la fin de la traduction ou de l’exécution (avec émission d’un message de diagnostic).

L’utilisation de g++ avec le paramètre de ligne de commande -Wall génère les diagnostics suivants:

 g++ -Wall -c -g -MMD -MP -MF "build/Debug/MinGW-Windows/main.od" -o build/Debug/MinGW-Windows/main.o main.cpp main.cpp: In function 'int main(void)': main.cpp:17:16: warning: format '%d' expects a matching 'int' argument [-Wformat=] printf("%d"); ^ 

C’est très utile, n’est-ce pas?

gcc/g++ vérifie également si les spécificateurs de format correspondent réellement aux types de parameters. C’est vraiment cool pour le débogage.

Selon cette documentation , les arguments supplémentaires doivent être au moins aussi nombreux que les spécificateurs de format du premier argument. Cela semble être un comportement indéfini.

Quels avertissements / messages avez-vous reçus en utilisant vos compilateurs? J’ai couru cela avec gcc (Ubuntu 4.8.2-19ubuntu1) et j’ai eu un avertissement

 warning: format '%d' expects a matching 'int' argument [-Wformat=] printf("%d\n"); ^ 

et l’exécuter aussi “sortie de déchets”. ici, le gcc est si habile pour parsingr l’expression de format et demande au codeur de fournir un nombre correspondant d’arguments.

ce que je pense se produit: la signature de fonction de printf est indépendante du comportement du code compilé. au moment de la compilation, le compilateur se soucie uniquement de vérifier si au moins un argument est présent et continue. toutefois, la fonction compilée parsingra d’abord l’expression de format et, en fonction de cela, lira d’autres arguments à partir de la stack d’arguments des fonctions. dans ce cas, il attend simplement les valeurs appropriées (int, float, etc.) et les utilise. ainsi, si vous ne spécifiez pas l’argument, aucune place sur la stack d’appels de fonction n’est réservée et printf lit toujours la mémoire aléatoire (dans ce cas, au premier emplacement). ceci explique également la sortie “garbage”, qui sera différente chaque fois que vous appelez le binary. vous pouvez même étendre le code à

 #include  int main(void) { printf("%d\n%d\n"); return 0; } 

et obtenez deux nombres de déchets différents 🙂

là encore, cela dépendra de l’environnement / du processus / du compilateur avec les valeurs lues. “comportement non spécifié” est ce qui décrit le mieux cet effet, parfois zéro, parfois autre.

J’espère que cela clarifie votre problème!

Dans le contexte de printf () et fprintf (), conformément à la clause 7.21.6.1 de la norme C11 du C standard, “Si les arguments sont insuffisants pour le format, le comportement est indéfini. Si le format est épuisé tant qu’il rest des arguments, les arguments en excès sont les suivants: évalués (comme toujours) mais sont par ailleurs ignorés “.