Comportement de printf lors de l'impression d'un% d sans fournir de nom de variable

StackOverflow https://stackoverflow.com/questions/437816

  •  22-07-2019
  •  | 
  •  

Question

Je viens de rencontrer un problème étrange, j'essaie d'imprimer une variable entière, mais j'ai oublié de spécifier le nom de la variable, c'est-à-dire.

printf("%d");

au lieu de

printf("%d", integerName);

Étonnamment, le programme compile, il existe une sortie et ce n’est pas aléatoire. En fait, il s’agit bien du nombre entier que je voulais imprimer en premier lieu, m-1.

L'instruction erronée printf restituera systématiquement m-1 aussi longtemps que le programme continuera à s'exécuter ... En d'autres termes, il se comportera exactement comme si l'instruction se lisait

.
printf("%d", m-1);

Quelqu'un sait-il la raison de ce comportement? J'utilise g ++ sans aucune option de ligne de commande.

#include <iostream>
#define maxN 100
#define ON 1
#define OFF 0

using namespace std;

void clearArray(int* array, int n);
int fillArray(int* array, int m, int n);

int main()
{
    int n = -1, i, m;
    int array[maxN];
    int found;

    scanf("%d", &n);

    while(n!=0)
    {
        found=0;
        m = 1;
        while(found!=1)
        {
            if(m != 2 && m != 3 && m != 4 && m != 6 && m != 12)
            {
                clearArray(array, n);
                if(fillArray(array, m, n) == 0)
                {
                    found = 1;
                }
            }
            m++;
        }

        printf("%d\n");

        scanf("%d", &n);
    }

    return 0;
}

void clearArray(int* array, int n)
{
    for(int i = 1; i <= n; i++)
        array[i] = ON;
}

int fillArray(int* array, int m, int n)
{
    int i = 1, j, offCounter = 0, incrementCounter;

    while(offCounter != n)
    {
        if(*(array+i)==ON) 
        {
            *(array+i) = OFF;
            offCounter++;       
        }
        else 
        {
            j = 0;
            while((*array+i+j)==OFF)
            {
                j++;
            }
            *(array+i+j) = OFF;
            offCounter++;           
        }
        if(*(array+13) == OFF && offCounter != n) return 1;
        if(offCounter ==n) break;

        incrementCounter = 0;       
        while(incrementCounter != m)
        {
            i++;
            if(i > n) i = 1;
            if(*(array+i) == ON) incrementCounter++; 
        }       
    }

    return 0;
}
Était-ce utile?

La solution

Vous dites que "étonnamment, le programme compile". En fait, ce n’est pas surprenant du tout. Camp; C ++ permet aux fonctions d'avoir des listes d'arguments variables. La définition de printf ressemble à ceci:

int printf(char*, ...);

Le "" ..." signifie qu'il n'y a aucun argument optionnel ou plus à la fonction. En fait, l'une des principales raisons pour lesquelles C a des arguments optionnels est de prendre en charge la commande printf & amp; famille de fonctions scanf.

C n’a aucune connaissance particulière de la fonction printf. Dans votre exemple:

printf("%d");

Le compilateur n'analyse pas la chaîne de format et ne détermine pas un argument entier. C'est un code C parfaitement légal. Le fait qu'il vous manque un argument est un problème sémantique qui n'apparaît qu'au moment de l'exécution. La fonction printf supposera que vous avez fourni l'argument et allez le chercher sur la pile. Il va ramasser tout ce qui se passe là-bas. Il se trouve que dans votre cas particulier, vous imprimez la bonne chose, mais il s’agit d’une exception. En général, vous obtiendrez des données erronées. Ce comportement varie d'un compilateur à l'autre et varie également en fonction des options de compilation que vous utilisez. Si vous activez l'optimisation du compilateur, vous obtiendrez probablement des résultats différents.

Comme indiqué dans l'un des commentaires de ma réponse, certains compilateurs ont "lint" et "lint". comme des capacités qui peuvent réellement détecter les appels printf / scanf erronés. Cela implique que le compilateur analyse la chaîne de format et détermine le nombre d'arguments supplémentaires attendus. C'est un comportement très spécial du compilateur qui ne détectera pas les erreurs dans le cas général. c’est-à-dire si vous écrivez votre propre " printf_better " fonction qui a la même signature que printf, le compilateur ne détectera pas si des arguments manquent.

Autres conseils

Ce qui se passe ressemble à ceci.

printf("%d", m);

Sur la plupart des systèmes, l'adresse de la chaîne sera insérée dans la pile, puis 'm' en tant qu'entier (en supposant qu'il s'agisse d'un int / short / char). Il n'y a pas d'avertissement, car printf est en principe déclaré comme 'int printf (const char *, ...);' - le ... signifiant 'tout va bien'.

Donc, comme tout est permis, il se passe des choses étranges lorsque vous y mettez des variables. Tout type entier plus petit qu'un int va comme un int - des choses comme ça. Ne rien envoyer est également acceptable.

Dans l'implémentation printf (ou au moins une implémentation "simple"), vous trouverez l'utilisation de va_list et de va_arg (les noms diffèrent parfois légèrement en fonction de la conformité). C’est ce qu’une implémentation utilise pour contourner la partie '...' de la liste des arguments. Le problème ici est qu’il n’ya PAS de vérification de type. Comme il n’existe pas de vérification de type, printf extraira des données aléatoires de la pile d’exécution lorsqu’il examine le format de chaîne ("% d") et pense qu’il est supposé être un 'int' suivant.

Une photo prise au hasard dans le noir indiquerait que l'appel de fonction que vous avez effectué juste avant printf a probablement passé 'm-1' en tant que deuxième paramètre? C'est l'une des nombreuses possibilités - mais il serait intéressant que ce soit le cas. :)

Bonne chance.

À propos - la plupart des compilateurs modernes (GCC, je crois?) ont des avertissements qui peuvent être activés pour détecter ce problème. Lint fait aussi bien je crois. Malheureusement, je pense qu'avec VC, vous devez utiliser le drapeau / analyse au lieu d’être gratuit.

Vous regardez dans la pile. Modifiez les valeurs de l'optimiseur, ce qui peut changer. Modifiez l'ordre des déclarations de vos variables (en particulier) m . Faites de m une variable de registre. Faites de m une variable globale.

Vous verrez certaines variations dans ce qui se passe.

Ceci est similaire aux célèbres hacks de saturation de mémoire tampon que vous obtenez lorsque vous effectuez des E / S simplistes.

Bien que je doute fortement que cela aboutirait à une violation de mémoire, l'entier que vous obtenez est un déchet non défini.

Vous avez trouvé un un comportement. Cela aurait pu être n'importe quel autre comportement, y compris un accès mémoire non valide.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top