Valeur des variables chars et ints non affichés a la fin :/

Résolu/Fermé
AzertyFunction - Modifié le 2 juin 2022 à 14:42
mamiemando Messages postés 33166 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 juillet 2024 - 12 juin 2022 à 22:47
Bonsoir tout le monde,

J'ai une petite question :

Je veux afficher la valeur d'une variable
char
et d'une variable
int
à la fin de mon programme (sachant que les valeurs seront différentes en fonction du cas choisi dans le
switch
).
  • Au lieu d'afficher
    "vous avez 10 points de vie"
    lorsque l'on choisit par exemple le cas
    4
    du
    switch
    (classe guerrier), le programme affiche à la fin
    "vous avez 6421988 points de vie"
    .
  • Et au lieu d'afficher
    "votre classe est guerrier"
    lorsque l'on choisit par exemple le cas
    4
    du
    switch
    (classe guerrier), le programme affiche à la fin
    "votre classe est *des caractères spéciaux*"
    .


int main () 
{
    int classe = 0;
    char ClasseN;
    int HP = 0;
    B:
    printf("Quel est votre classe ?\n"); 
    printf("1. Cleric\n"); 
    printf("2. Mage\n"); 
    printf("3. Voleur\n"); 
    printf("4. Guerrier\n"); .
    scanf("%d", &classe); 
    switch (classe) {
        case 1:
        printf("Vous etes un cleric.");
        ClasseN = 'cleric';
        HP=8;
        break;
        case 2:
        printf("Vous etes un mage.");
        ClassN = 'mage';
        HP=4;
        break;
        case 3:
        printf("Vous etes un voleur.");
        ClasseN = 'voleur';
        HP=6;
        break;
        case 4:
        printf("Vous etes un guerrier.");
       ClasseN = 'guerrier';
        HP=10;
        break;
        default:
        system("cls");
        printf("Erreur!");
        getchar();
        getchar();
        goto B;

    }
    system("cls"); .
    printf("Votre classe est %s\n", &ClasseN);
    printf("Vos points de vie : %d HP\n", &HP);
    return 0;
}


Comment résoudre le problème ?

Merci d'avance pour vos réponses !

EDIT : Ajout des balises de code (la coloration syntaxique).
Explications disponibles ici : ICI

Merci d'y penser dans tes prochains messages.

5 réponses

NHenry Messages postés 15140 Date d'inscription vendredi 14 mars 2003 Statut Modérateur Dernière intervention 30 juin 2024 333
1 juin 2022 à 19:03
char ClasseN;
Puis
ClasseN = 'guerrier';
En C les chaines sont marquées par des "...", les caractères par des '...'
Donc tu ne mets que 'g' dans ClasseN

En C, c'est char* pour les chaine de caractères.

Pour le printf, pas besoin de mettre le pointeur, tu mets juste la valeur :
printf("Vos points de vie : %d HP\n", HP);

Petite remarque, sur Linux, le retour à la ligne c'est "\n", sur Window$ "\r\n"
0
[Dal] Messages postés 6193 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 4 juillet 2024 1 088
Modifié le 2 juin 2022 à 16:00
Petite remarque, sur Linux, le retour à la ligne c'est "\n", sur Window$ "\r\n"

Si tu parles des printf(), pour faire un retour à la ligne sous Windows ou Linux, tu ajoutes \n en fin de chaîne et c'est tout.

L'implémentation de la bibliothèque standard et l'API de l'OS se chargent de rajouter un \r sous Windows pour l'affichage sur le terminal.
0
mamiemando Messages postés 33166 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 juillet 2024 7 761
Modifié le 2 juin 2022 à 15:24
Bonjour,

Pour reprendre ce que dit NHenry, voici à quoi pourrait ressembler ton programme une fois corrigé :

#include <stdio.h>

int main (){
    int classe = 0;
    char * classe_name = NULL;
    int hp = 0;

    do {
        printf(
            "Quel est votre classe ?\n"
            "1. Cleric\n"
            "2. Mage\n"
            "3. Voleur\n"
            "4. Guerrier\n"
        );
        scanf("%d", &classe);
        switch (classe) {
            case 1:
                classe_name = "cleric";
                hp = 8;
                break;
            case 2:
                classe_name = "mage";
                hp = 4;
                break;
            case 3:
                classe_name = "voleur";
                hp = 6;
                break;
            case 4:
                classe_name = "guerrier";
                hp = 10;
                break;
            default:
                //system("cls");
                printf("Erreur!");
                getchar();
                getchar();
        }
    } while (!classe_name);
    //system("cls"); .
    printf(
        "Votre classe est %s\n"
        "Vos points de vie : %d hp\n",
        classe_name,
        hp
    );
    return 0;
}


Corrections supplémentaires apportées :
  • Corrections de typos diverses (nom de variables, accents manquants)
  • Ajout du
    #include
    manquant.
  • Conventions de nommage : généralement les variables s'écrivent en minuscule :
  • printf
    dans les
    case
    : redondant avec le
    printf
    final.
  • Pas besoin de faire plein de
    printf
    à la suite, un seul suffit (un appel de fonction n'est pas complètement gratuit, ce sera donc plus efficace dans l'absolu, même si ici ce sera imperceptible)
  • Il faut éviter les labels et les
    goto
    , c'est une mauvaise habitude de programmation et dans 99% des cas on peut s'en passer.
  • Il faut éviter la fonction
    system()
    : elle rend ton programme dépendant de la plateforme (ici windows). Pour rendre le programme portable à pour d'autres systèmes d'exploitation (MacOS, Linux), il faudrait s'en passer ou utiliser une librairie qui permet de s'affranchir de ces considérations (e.g.,
    ncurses
    ), voir cette discussion.


Quelques clarifications sur les pointeurs

Comme le dit NHenry, il y a une confusion de ta part sur quand passer l'adresse d'une variable (e.g.
&classe
) et quand ne pas le faire. C'est souvent parce que
scanf
n'est pas bien expliqué aux gens qui débutent en C. Quand on appelle une fonction (e.g.,
scanf
) les paramètres sont recopiés en pile. Cela signifie que si la fonction appelée modifie un de ces paramètre, elle modifie la recopie, et une fois sorti de la fonction l'original n'est pas modifié. Un bon exemple pour s'en convaincre :

#include <stdio.h>

void incrementer(int x) {
    x++;
}

int main(){
    int x = 7;
    printf("%d\n", x); // 7
    incrementer(x);
    printf("%d\n", x); // Pas 8, 7 !
    return 0;
}


Pour s'en sortir il faut passer l'adresse de la variable à modifier. Ainsi, c'est l'adresse qui est recopiée en paramètre, mais celle-ci correspond bien à l'adresse de la variable originale à l'aide de l'opérateur unaire
&
. C'est ce qu'on appelle un passage par pointeur. Le code précédent se corrige donc ainsi :

#include <stdio.h>

void incrementer(int * px) {
    (*px)++;
}

int main(){
    int x = 7;
    printf("%d\n", x); // 7
    incrementer(&x);
    printf("%d\n", x); // 8
    return 0;
}


scanf("%d", &hp)
découle du même problème : on veut modifier
hp
, mais la passer directement reviendrait à modifier une copie, et c'est pourquoi
scanf
attend l'adresse de
hp
(donc
&hp
) afin en arrière boutique de modifier
*(&hp)
. Ainsi, scanf est capable de modifier un de ses "paramètres" (en réalité : une variable dont on a passé l'adresse en paramètre = passage par pointeur).

Par contre,
printf("hp = %d\n", hp);
n'a pas besoin de modifier ses paramètres, donc un passage par pointeur n'est pas requis et ne ferait qu'alourdir sont utilisation. On passe donc directement les variables à afficher en paramètres (passage par recopie).

Quelques clarifications sur les chaînes de caractères en C

Dans l'absolu, une chaîne est arbitrairement longue. Or en C un type a une taille fixée à la compilation. À cause de cette contradiction, il n'y a pas de type de base pour faire une chaîne de caractère. C'est d'ailleurs pour ça qu'en C++, la chaîne est définie au travers d'un objet.

Alors si on revient au C, comment répondre à ce problème. On exploite tout simplement la notion de passage par pointeur (on passe l'adresse du bloc mémoire qui contient la chaîne), car un pointeur (quelque soit son type) fait toujours une taille bien précise (32 bits sur un système 32 bits, 64 bits sur un systèmes 64 bits, etc). A ce stade on sait où commence la chaîne, mais pas où elle s'arrête, alors comment faire.

Une solution aurait pu consister à passer la longueur de la chaîne en paramètre, mais on comprend assez rapidement qu'à l'usage, ça n'est pas pratique du coup. Du coup,
printf
adopte une convention (de même que toutes les fonctions de la lib C manipulant des chaînes de caractères) : le bloc contenant la chaîne de caractère est considéré terminé dès qu'on rencontre le caractère
'\0'
(= un octet mis à 0).

Une fois qu'on a compris ça, on s'aperçoit que déclarer une chaîne de caractère fait en réalité pleins de choses. En effet, quand tu écris
char * s = "toto";
, cela signifie en réalité :
  • "crée un bloc de 5 octets, contenant les caractères
    't' 'o' 't' 'o' '\0'
    ,
  • puis mémorise dans
    s
    l'adresse de ce bloc,
  • puis considère que c'est un pointeur de type
    char *
    (ce qui permet d'avoir
    *s
    ou
    s[i]
    bien définis).


Retour à ton code

À l'époque, tu créais
char ClasseN
(un octet) et donc le fait d'écrire
ClasseN = "Guerrier"
revenait à ne copier que le caractère
'G'
. Et par la suite, comme
printf(" ... %s ...", ...)
attend un
char *
, tu "trichais" en passant
&ClasseN
. Or malheureusement, à la suite de
ClasseN
, rien ne garantit qu'il y a un octet nul dans le segment de mémoire assigné à ton programme par le système d'exploitation, donc potentiellement,
printf
peut lire une "chaîne" arbitrairement longue jusqu'à déclencher une erreur de segmentation (c'est à dire que ton programme déclenche une erreur mémoire car il lit en dehors de son segment). Donc, selon l'état de la mémoire, le programme pouvait non seulement ne pas faire ce que tu voulais, mais en plus planter :-)

J'espère que mes explications t'auront aidé, et si tu as besoin de clarifications, n'hésite pas à poser tes questions.

Bonne continuation
0
[Dal] Messages postés 6193 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 4 juillet 2024 1 088
2 juin 2022 à 19:09
Salut AzertyFunction,

Les observations de NHenry et de mamiemando répondent à ton problème immédiat et mamiemando a très bien expliqué le pourquoi des problèmes que tu avais et de ses recommandations.

En ce qui me concerne, j’ajouterais que si ton intention est effectivement de créer un jeu de rôles, clairement, la direction que tu prends en créant des variables isolées pour stocker les informations concernant un joueur n'est pas adaptée.

Tu as intérêt à créer un type
struct personnage
, utiliser les enum à bon escient et d'utiliser aussi une struct pour définir les classes existantes et les attributs de chaque classe pour mettre de l'intelligence dans les données et te faciliter la vie.

Renseigne toi sur ces aspects du C, si tu n'es pas familier avec eux.

Si tu as besoin de code illustrant ces techniques, dis le et je peux t'écrire quelques lignes pour t'éclairer sur ces sujet.

Cela te permettait :
  • de gérer facilement le cas où tu ajoutes une nouvelle "classe" de personnage, ou où tu changes la quantité de hp sans toucher au code
  • de gérer facilement le cas où tu veux ajouter un attribut supplémentaire à une "classe" (en plus des hp : dextérité, points de magie, localisation dans monde, etc.)
  • de regrouper toutes les données concernant un joueur dans une structure de données pouvant être renvoyée / remplie par une fonction à laquelle tu passes l'enum correspondant à la classe à créer
  • de créer un tableau C ou une autre structure de données statique ou dynamique pour stocker des personnages joueurs et non joueurs et parcourir ces données
  • de créer un groupe de personnages en combat et de les passer à une fonction gérant le combat
  • etc.


Dal
0
[Dal] Messages postés 6193 Date d'inscription mercredi 15 septembre 2004 Statut Contributeur Dernière intervention 4 juillet 2024 1 088
Modifié le 3 juin 2022 à 10:37
@mamiemando et @AzertyFunction:

le 2/06/2022 à 15:24 mamiemando a écrit :
tu créais char ClasseN (un octet) et donc le fait d'écrire ClasseN = "Guerrier" revenait à ne copier que le caractère 'G'

Pas exactement. Comme
"Guerrier"
est une chaîne C (littérale), elle est stockée en mémoire du programme comme un type tableau de
const char[8]
.

Or, une expression de type tableau, en C, est toujours évaluée à l'adresse du premier élément du tableau (non seulement à droite d'un signe égal, mais aussi en paramètre d'une fonction, etc.), donc, à droite du signe égal, elle est évaluée comme un
char *
.

En fait, ce que l'on tente d'affecter avec ce code erroné, ce n'est pas la lettre "G", mais l'adresse mémoire où se trouve stockée la chaîne littérale.

C'est ce qui explique que lorsque l'on compile ce type de code avec les Warnings on obtient un avertissement du compilateur qui dit ceci :

$ gcc -Wall -Wextra 37606270.c
37606270.c: In function ‘main’:
37606270.c:4:17: warning: initialization of ‘char’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]
char ClasseN = "Guerrier";
^~~~~~~~~~


@AzertyFunction:

C'est aussi une autre recommandation que l'on peut te faire.

En C, ce n'est pas parce que le programme compile et que l'on obtient un exécutable qu'il est correct ou même qu'il a du sens. La flexibilité du langage fait que l'on peut écrire plein de choses qui n'ont pas de sens mais qui ne sont pas incorrectes syntaxiquement, et le C considérera que le programmeur est assez grand et qu'il sait ce qu'il fait et produira un exécutable (qui fera n'importe quoi, ou plantera).

Compiler son code avec les Warnings (avec gcc option
-Wall
au minimum, ou mieux
-Wall -Wextra
) permet de disposer d'alertes sur du code qui peut compiler mais qui n'a pas de sens ou est très probablement erroné (dans la quasi totalité des cas, c'est un bogue).

Il ne pas ignorer les Warnings, mais comprendre pourquoi ils se manifestent. En comprenant leur signification, on apprend aussi le fonctionnement intime du C.

Dans le cas de ton code, les avertissements auraient pointé du doigt les lignes de code problématiques qui ont été signalées par NHenry et de mamiemando.

$ gcc -Wall -Wextra 37606270.c
37606270.c: In function ‘main’:
37606270.c:7:5: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
printf("Quel est votre classe ?\n");
^~~~~~
37606270.c:7:5: warning: incompatible implicit declaration of built-in function ‘printf’
37606270.c:7:5: note: include ‘<stdio.h>’ or provide a declaration of ‘printf’
37606270.c:1:1:
+#include <stdio.h>
int main ()
37606270.c:7:5:
printf("Quel est votre classe ?\n");
^~~~~~
37606270.c:11:30: error: expected expression before ‘.’ token
printf("4. Guerrier\n"); .
^
37606270.c:16:19: warning: character constant too long for its type
ClasseN = 'cleric';
^~~~~~~~
37606270.c:16:19: warning: overflow in conversion from ‘int’ to ‘char’ changes value from ‘1701996899’ to ‘99’ [-Woverflow]
37606270.c:21:9: error: ‘ClassN’ undeclared (first use in this function); did you mean ‘ClasseN’?
ClassN = 'mage';
^~~~~~
ClasseN
37606270.c:21:9: note: each undeclared identifier is reported only once for each function it appears in
37606270.c:21:18: warning: multi-character character constant [-Wmultichar]
ClassN = 'mage';
^~~~~~
37606270.c:26:19: warning: character constant too long for its type
ClasseN = 'voleur';
^~~~~~~~
37606270.c:26:19: warning: overflow in conversion from ‘int’ to ‘char’ changes value from ‘1818588530’ to ‘114’ [-Woverflow]
37606270.c:31:18: warning: character constant too long for its type
ClasseN = 'guerrier';
^~~~~~~~~~
37606270.c:31:18: warning: overflow in conversion from ‘int’ to ‘char’ changes value from ‘1919509874’ to ‘114’ [-Woverflow]
37606270.c:35:9: warning: implicit declaration of function ‘system’ [-Wimplicit-function-declaration]
system("cls");
^~~~~~
37606270.c:37:9: warning: implicit declaration of function ‘getchar’ [-Wimplicit-function-declaration]
getchar();
^~~~~~~
37606270.c:37:9: note: ‘getchar’ is defined in header ‘<stdio.h>’; did you forget to ‘#include <stdio.h>’?
37606270.c:42:20: error: expected expression before ‘.’ token
system("cls"); .
^
37606270.c:44:34: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
printf("Vos points de vie : %d HP\n", &HP);
~^ ~~~
%ls


Il faut apprendre à utiliser les Warnings :-)
0
mamiemando Messages postés 33166 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 juillet 2024 7 761
Modifié le 3 juin 2022 à 13:42
Merci pour ta vigilance Dal, tu as parfaitement raison, c'est effectivement le début de l'adresse de la chaîne
Guerrier
(et non le premier caractère) qui était recopié.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
Cprog Messages postés 1 Date d'inscription samedi 11 juin 2022 Statut Membre Dernière intervention 11 juin 2022
Modifié le 12 juin 2022 à 22:47
Bonjour,
Voici un exemple de corrigé du problème. (Je suis débutant donc il se peut que le code soit peu académique surtout pour la taille de la variable ClasseN).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main ()
{
    int classe = 0;
    char ClasseN[256];
    int HP = 0;
    B:
    printf("Quel est votre classe ?\n");
    printf("1. Cleric\n");
    printf("2. Mage\n");
    printf("3. Voleur\n");
    printf("4. Guerrier\n");
    scanf("%d", &classe);
    switch (classe) {
        case 1:
        printf("Vous etes un cleric.");
        strcpy(ClasseN, "cleric");
        HP=8;
        break;
        case 2:
        printf("Vous etes un mage.");
        strcpy(ClasseN, "mage");
        HP=4;
        break;
        case 3:
        printf("Vous etes un voleur.");
        strcpy(ClasseN, "voleur");
        HP=6;
        break;
        case 4:
        printf("Vous etes un guerrier.");
        strcpy(ClasseN, "guerrier");
        HP=10;
        break;
        default:
        system("cls");
        printf("Erreur!");
        getchar();
        getchar();
        goto B;

    }
    system("cls");
    printf("Votre classe est %s\n", &ClasseN);
    printf("Vos points de vie : %d HP\n", HP);
    return 0;
}
0
mamiemando Messages postés 33166 Date d'inscription jeudi 12 mai 2005 Statut Modérateur Dernière intervention 2 juillet 2024 7 761
12 juin 2022 à 22:47
Merci mais je l'avais déjà corrigé dans mon message :-) Par ailleurs, vise à utilise les balises de code pour le C la prochaine fois (tu peux choisir le langage en cliquant sur la partie de droite du bouton permettant de mettre les balises de code).
0