Pendu en C
Forum Programmation : Pendu en C
Hello,
J'ai un petit souci.
Alors voilà, j'ai essayé de faire un pendu (en me basant sur l'idée de base du SdZ mais sans regarder le code qu'il employait).
J'ai réussi sans trop de soucis le pendu pour un mot que je définissais à la base.
Mais c'est quand j'essaie de mettre un mot au hasard, par rapport à une liste de mots prédéfinie que je galère un peu.
Contrairement au SDZ (j'avais pas trop d'idée pour commencer ça), j'ai préféré utiliser une structure qu'un fichier pour stocker les mots.
Voici le code :
Code :
|
Je pense (sûr même) que la partie qui pose problème est celle-ci :
Voici la structure qui ne semble pas poser de problèmes (qui contient 7 mots pour le moment) :
struct Dico
{
char mot[20];
};
typedef struct Dico Dico;
Dico mots_pendu[] = {"ALICE", "ANTOINE", "ROMEO", "JULIETTE", "BERNARD", "ANDRE", "BAPTISTE"};
Ensuite, ce que je n'arrive pas (dernière étape avant la fin du jeu pourtant) c'est d'allouer le même nombre de lettre au "mot_trouve" que celui que je prends au hasard dans la structure, en lui assignant ce nombre fois le caractère étoiles.
Pour qu'en console, ça mette ******* (et que ça fasse apparaître les lettres quand l'utilisateur en trouve -> déjà fait pour ça);
=> mot_trouve = malloc (COUNT_L (&mots_pendu[nombreal].mot[0]) * sizeof(char));
c'est apparemment une des lignes qui fait que quand je compile, j'ai un écran vide =D
J'aimerais bien mettre dans mot autant d'étoiles qu'il y a de lettres à trouver, mais je ne sais pas comment faire.... merci
edit : j'aimerais bien ne pas avoir à utiliser de fonctions comme strcpy pour apprendre vraiment.
Merci beaucoup !
Message édité par XmichouX le 05-10-2009 à 21:04:25
Juste pour info, tu peux définir le typedef à la définition de la structure:
Code :
|
Bref,
Effectivement lorsque tu fais un tableau statique: type var[taille], alors taille doit être une constante: la variable est alloué statiquement. Quand le compilateur vois ça, il sait très bien qu'il aura besoin de (taille*sizeof(type)]) octets à allouer sur la pile, alors il peux le préparer à l'avance. Si tu veux mettre un tableau de taille non défini à la compilation, il va falloir que le programme alloue lui-même la mémoire dont il a besoin. C'est donc effectivement une allocation dynamique sur le tas qu'il te faut faire.
Par contre, je pense que tu galère parce que ta fonction COUNT_L() ne fait pas ce que tu en attends d'elle. ![]()
Code :
|
Tu n'incrémentes jamais i => boucle infinie si la chaine n'est pas vide. ![]()
Je te corrige:
Code :
|
mais tu peux faire plus simple (un for qui ne fait rien):
Code :
|
Bon, je ne teste pas ton programme, je lis juste rapidement, et je te dis mes remarques:
dans nombreAleatoire(), ne fait pas le srand() à chaque fois que tu souhaites un nombre aléatoire. Tu le fais une fois au début, et après tu n'utilises que rand(). En faisant comme tu fais, sans parler de la loi du khi², tu n'es pas sûr d'avoir un bon générateur aléatoire. ![]()
Dans ta fonction compare(), tu peux optimiser en arrêtant la boucle lorsque "chaine1[i] == chaine2[i]". Tu peux aussi faire un for qui ne sert à rien. ![]()
Sinon le strcmp retourne -1 (si chaine1[i] < chaine2[i]), 0 ou 1.
Sinon des petites règles pour bien utiliser malloc:
- quand tu écris le code malloc, la première chose à faire ensuite, c'est d'écrire la ligne du free()
- tester tous les pointeur lorsque avant l'utilisation de tout *var ou var->...
Message édité par CRicky le 05-10-2009 à 21:26:04
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hey Cricky,
Merci pour ta réponse ![]()
Merci pour le tuyau pour le typedef, je ne savais pas.
En effet, j'avais oublié le i ![]()
ça marche mieux maintenant ![]()
En fait finalement, ça a été simple d'initialiser la chaîne avec des *. Je m'y suis pris comme ça :
void INITIALISE_C (char chaine[], int n_c)
{
int i = 0;
for (i = 0; i < n_c - 1; i++)
{
chaine[i] = '*';
}
}
Au départ, je pensais faire un while (..[i] != '\0'), mais ça ne marche pas. Je pensais que vu que j'avais spécifié un char, seul le dernier indice du tableau allait se mettre à \0, mais apparemment ça n'était pas le cas. Mais en même temps, je ne sais pas comment se passe l'allocatio navec malloc (ce qui est initialement mis en mémoire à cet endroit lors de l'allocation).
en ayant fait : int count = COUNT_L (&mots_pendu[nombreal].mot[0]);
![]()
Merci beaucoup pour ton aide (comme d'habitude
)
-------
Pour le nombreAleatoire, en fait, je ne connais pas du tout la fonction srand, ça, c'est vraiment un code que j'ai pris tout cuit du sdz, d'ailleurs, ça ne me dérange pas de bien comprendre ! Mais vu que c'est dans une fonction, comment puis-je faire la première fois avec le s, et les autres fois sans ?
Pour la fonction compare(), je ne vois pas trop ce que tu veux dire en fait =D (tu parles toujours de mettre un compteur dans la fonction ?)
Merci (je vais peaufiner encore un peu demain
)
Message édité par XmichouX le 06-10-2009 à 08:44:04
Répondre à XmichouX
| Citation : Au départ, je pensais faire un while (..[i] != '\0'), mais ça ne marche pas. Je pensais que vu que j'avais spécifié un char, seul le dernier indice du tableau allait se mettre à \0, mais apparemment ça n'était pas le cas. Mais en même temps, je ne sais pas comment se passe l'allocatio navec malloc (ce qui est initialement mis en mémoire à cet endroit lors de l'allocation). |
Quand tu fais un malloc, le contenu n'est pas mis à 00. Donc, soit tu fais un memset() avec '\0' pour mettre '\0' partout, soit (et c'est mieux), tu mets juste '\0' au premier caractère pour indiquer une chaine vide.
Lorsque tu fais une copie, il faut toujours penser à copier un caractère de plus pour copier le '\0', et quand tu initialise une chaine avec des '*', pense à ajouter un '\0' à la fin.
Bref, si tu penses toujours au '\0', tu n'aura jamais besoin d'utiliser un nombre de caractères (c'est l'avantage des fonction str...() sur les mem...() pour les chaines de caractères).
| Citation : Pour le nombreAleatoire, en fait, je ne connais pas du tout la fonction srand, ça, c'est vraiment un code que j'ai pris tout cuit du sdz, d'ailleurs, ça ne me dérange pas de bien comprendre ! Mais vu que c'est dans une fonction, comment puis-je faire la première fois avec le s, et les autres fois sans ? |
En fait le rand() est une fonction mathématique simple qui se base sur au moins une valeur précédente pour calculer la suivante. Par exemple: RAND<n> = (a * RAND<n-1> + c) % m où a,c et m sont des constantes.
Donc pour que rand() fonctionne bien, il faut l'avoir utilisé, mais au début on ne l'utilise pas, il faut donc définir une première valeur pour commencer. La première valeur c'est la graine (=> seed => seed random => srand) srand permet d'initialiser le générateur de nombres pseudo-aléatoire.
Afin d'avoir une séquence différente à chaque démarrage du programme, on utilise la valeur du temps actuel (time()), donc on a une valeur différente à chaque démarrage, ce qui donne une séquence différente.
Donc, le srand(), tu ne le lances qu'une seule fois pour initialiser le générateur de nombres pseudo-aléatoires, et pour obtenir des nombres aléatoires, tu n'utilises plus que rand().
| Citation : Pour la fonction compare(), je ne vois pas trop ce que tu veux dire en fait =D (tu parles toujours de mettre un compteur dans la fonction ?) |
Ce que je veux dire, c'est que toi tu recherches tous les caractères différents, mais si tu trouve un caractère différent, tu sais que les chaines sont différentes, alors il faut arrêter la boucle.
Exemple, si tu compares les 2 chaines suivantes:
abzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
abyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
1. Tu commences à lire 'a' == 'a', alors on continue à chercher une différence
2. 'b' == 'b' alors on continue à chercher une différence
3. 'z' > 'y' donc on sait que les chaines sont différentes, alors on s'arrête là et on gagne du temps à ne pas lire les autres caractères.
Code :
|
Le for ne fait rien mais boucle tant que l'on n'arrive pas à l'a fin d'un chaine et tant que les caractères sont identiques. Donc, après le for, i indique soit une fin de chaine, soit un caractère différent.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hello !
| Citation : tu mets juste '\0' au premier caractère pour indiquer une chaine vide. |
Oki, je ne savais pas que mettre un \0 au premier caractère allait initialiser toute la chaîne ? (ou ce n'est pas le cas ?)
Pour les **, effectivement, après 2-3 mots, j'ai remarqué qu'un caractère s'était ajouté au mot_trouve (au lieu du \0) et donc ça ne marchait pas pour ce mot. Donc je m'en suis occupé ![]()
| Citation : Donc, le srand(), tu ne le lances qu'une seule fois pour initialiser le générateur de nombres pseudo-aléatoires, et pour obtenir des nombres aléatoires, tu n'utilises plus que rand(). |
Ok, donc si j'ai bien compris, ma fonction devrait plutôt ressembler à ça ?
Code :
|
J'écris ça en pensant rejouer plusieurs fois (pas encore fait) dans la même exécution du programme.
Pour ta fonction compare, en fait tout se joue là-dessus ?
| Citation : return chaine1[i] - chaine2[i]; |
Vu que la fonction renvoie un int, si les deux chaînes sont identiques, alors ça retournera -1 ? (\ 0 - \ 0 = 0 - 0 (ou NULL - NULL ?
)
Fallait y penser quand même si c'est bien ça ![]()
Sinon pour le i++ et le ++i, cela cause une réelle différence dans une boucle for ?
Bien, maintenant, je vais essayer de stocker les caractères tapés dans un tableau, et faire en sorte, que si l'user tape le même caractère, il ne soit pas pénalisé ![]()
Merci!
Message édité par XmichouX le 06-10-2009 à 20:37:23
Répondre à XmichouX
Bonjour à tous,
-Pour le '\0', tu veux dire quoi par initialiser toute la chaîne ?
Peu importe la valeur que tu affectes à ta variable au départ, celle-ci sera initialisé même lorsque tu fais:
Code :
|
-En ce qui concerne la fonction nombreAleatoire, le code est correct.
J'aimerais bien en savoir plus sur la loi de Khi² CRicky^^
Sinon ton code est correct.
-Pour compare(), je n'avais jamais vu un truc pareil concernant le code de CRicky
car je n'ai pas l'habitude de faire des traitements dans les parenthèses du for, comme quoi on en apprend tous les jours
Si j'ai bien compris dès qu'une des conditions (cond_1 && cond_2 && cond_3) n'est plus vérifiée, il suffit de retourné la différence des 2 cases comparées qui sera différente de 0, donc "chaines non identiques".
Si ça retourne 0, elles sont identiques, c'est bien ça ?
Pour i++: on retourne la valeur et après on l'incrémente.
Pour ++i: on incrémente la valeur et on la retourne.
Après je ne vois pas pourquoi il a choisi "++i" ici
A+
La vie ne vaut rien mais rien ne vaut la vie...
Répondre à akred3
Salut,
Par initialiser, pour un chaîne, c'est vrai que c'est pas évident. J'entendais un peu ça : char chaîne[nombre] = "";
Bon, j'ai retapé mon programme, il marche nickel :
Code :
|
Je vais mettre tes optimisations en commentaires CRicky, encore merci ![]()
edit : je viens d'avoir une mauvaise surprise. ça ne marche pas sur XP. Quand je tape des caractères, il me dit qu'ils sont mauvais. Pourtant, je vois bien le bon caractère s'afficher .... et la table de code est logiquement la même surtout que c'est du ASCII de base (pas étendu)..
Merci =)
Message édité par XmichouX le 07-10-2009 à 06:37:08
Répondre à XmichouX
Quand je parlais du '\0', ce que je voulais dire c'est que comme la chaine se termine toujours par '\0' en C, peu importe ce qui suit en mémoire puisqu'on ne va jamais le lire.
En C quand on écrit "toto", en fait, une fois compilé, c'est un tableau des caractères 't', 'o', 't', 'o' et '\0'. Du coup, quand on fait char var[] = "", et bien en fait on met un '\0' à l'adresse indiqué par var.
Pour ta fonction aléatoire, ça va marcher, mais moi, je pensais à quelque chose de beaucoup plus simple.
Dans le main:
Code :
|
Comme ça au tout début de ton programme, tu initialises le générateur, et après tu n'utilise que rand:
Code :
|
Pour le "return chaine1[i] - chaine2[i];", un caractère char est en fait un nombre codé (ASCII étendu ou latin-1) sur 1 octet, donc c'est juste un nombre de 0 à 255 inclus, avec par exemple '\0' = 0x00, '0' = 0x30.
Du coup, on compare les caractère par rapport à la position dans la table ASCII.
Donc si chaine1[i] == '\0' et chaine2[i] == '\0', ça retourne 0x00 - 0x00 = 0x00 => égalité.
si retourne < 0, alors la première chaine est soit plus courte, soit alphabétiquement (enfin suivant l'ordre ASCII...) avant la seconde.
pour le ++i et i++, akred3 a expliqué, alors je vais juste montrer la différence d'implémentation et vous expliquer pourquoi je n'utilise que des pre-incrémentation (++i). Si je code ++i par une fonction "int PreIncrement(int*)" et i++ par une fonction "int PostIncrement(int*)", alors l'implémentation sera:
Code :
|
Pour le post-increment, on doit passer par une valeur temporaire. Un "int" faisant généralement 4 octets, ça occupe 4 octets supplémentaire sur la pile à l'exécution. 4 octets ce n'est pas beaucoup donc, ça ne sert à rien de se limiter à ++i, on peut faire i++, ça ne changera rien.
En fait, c'est surtout en C++ que ça peut impacter, car si l'on fait la même chose avec des objets, ça crée un objet temporaire avec allocation d'une certaine mémoire, et appel du contructeur par recopie (en espérant qu'il n'ait pas de bug). Bref, c'est surtout en C++ en manipulant les objets que ça a un impact, c'est pourquoi, je prend toujours l'habitude d'utiliser le pre-incrément ++i. Je n'ai pas fait ça pour faire cette explication, c'est juste une habitude.
Même si i++ semble plus naturel que ++i, en fait, d'un point de vue logique, il est plus naturel de faire ++i, mais bon, tant que ce ne sont que des types de base, ça ne changera pas grand chose.
Pour le khi² en fait c'est le test du khi². C'est du calcul statistique (j'aime pas les stats
) qui permet de savoir si les valeurs correspondent bien à une distribution probabilistique (enfin il y a des dérivés du test).
http://fr.wikipedia.org/wiki/Test_du_khi_carr%C3%A9
| Citation : Si j'ai bien compris dès qu'une des conditions (cond_1 && cond_2 && cond_3) n'est plus vérifiée, il suffit de retourné la différence des 2 cases comparées qui sera différente de 0, donc "chaines non identiques".
|
Oui c'est ça.
| Citation : edit : je viens d'avoir une mauvaise surprise. ça ne marche pas sur XP. Quand je tape des caractères, il me dit qu'ils sont mauvais. Pourtant, je vois bien le bon caractère s'afficher .... et la table de code est logiquement la même surtout que c'est du ASCII de base (pas étendu).. |
Juste avant le "return -1;", affiche le code ASCII du caractère pour voir ce qui se passe:
Code :
|
C'est peut-être dû à un retour chariot qui traine, il faudrait peut-être vider le buffer du clavier après avoir entré la lettre.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hello,
| Citation : En C quand on écrit "toto", en fait, une fois compilé, c'est un tableau des caractères 't', 'o', 't', 'o' et '\0'. Du coup, quand on fait char var[] = "", et bien en fait on met un '\0' à l'adresse indiqué par var. |
Seulement ? Lorsque j'ai créé ce tableau :
char t_w_s_c[30] = "";
J'avais d'abord essayé sans rien définir (sans le "" ). Puis j'avais bouclé pour afficher tous les indices du tableau, la plupart contenaient un peu n'importe quoi.
Une fois mis le = "", toutes les cases étaient vides. Je pensais donc que faire char blabla[30] = "", vidait toute la chaine définie.
Okir pour l'incrémentation.
En fait, dans ton exemple :
int i;
for( i = 0 ; chaine1[i] != '\0' && chaine2[i] != '\0' && chaine1[i] == chaine2[i] ; ++i );
return chaine1[i] - chaine2[i];
Si une chaîne est plus courte que l'autre, la boucle va s'arrêter, mais alors forcément une des autres conditions (pourtant liées par des && ) ne sera pas respectée. C'est ça que je trouve un peu bizarre. De même si les deux premiers caractères des chaînes sont égaux, logiquement la boucle s'arrête non? (ou pas puisque les autres conditions ne sont pas vérifiées). C'est bizarre en fait à comprendre je trouve.
Pour les caractères, j'ai d'abord essayé sous Vista :
- Petite surprise lorsque je tape des caractères (au dessus de 7F), quand je tape un é dans la console (je ne sais plus si c'est considéré comme du ANSI ou du OEM quand on tape directement dans la console ^^), il m'affiche par exemple que j'ai tapé : 0XFFFFFF8A
pour le ç : 0xFFFFFF87
Bon, je suis pas bête
Finalement, j'ai remarqué que le dernier octet représentait le codage ASCII étendu OEM du caractère que je tapais dans la console (donc j'ai ma réponse pour la "question" juste avant). La question est : pourquoi tous ces FF avant ? Surtout que c'est censé être stocké dans un char !
- Maintenant, j'essaie sous XP :
Bizarre, j'ai pourtant recompilé avec le code printf ajouté sous Vista pour C/C sur mon xp l'exécutable. Il ne me sort pas la phrase sur XP pourtant la date de modif coïncide bien avec la dernière recompilation ... (j'ai pris dans Debug).
edit : Alors, là, j'ai une grosse surprise
!
(j'ai installé le compilateur sous xp et compilé le .c)..
Lorsque je tape un caractère normal, il me fait comme avant, juste le message d'erreur ! (sans le printf ajouté).. et lorsque je tape un caractère > 0x7F, il me fait exactement la même chose que sous Vista (cette fois avec le printf).
o_O
Merci, bizarre quand même !
Répondre à XmichouX
| Citation : Je pensais donc que faire char blabla[30] = "", vidait toute la chaine définie |
Je dirais que ça dépend de ton compilateur et de son optimisation. Quoi qu'il en soit, du point de vue du programmeur, tu ne dois pas considérer que le reste est à 0. Si tu veux des 0, il faut faire un memset().
| Citation : Si une chaîne est plus courte que l'autre, la boucle va s'arrêter, mais alors forcément une des autres conditions (pourtant liées par des && ) ne sera pas respectée. C'est ça que je trouve un peu bizarre. De même si les deux premiers caractères des chaînes sont égaux, logiquement la boucle s'arrête non? (ou pas puisque les autres conditions ne sont pas vérifiées). C'est bizarre en fait à comprendre je trouve. |
C'est un problème de logique: si A et B sont des conditions, alors NON(A ET B) <=> (NON A) OU (NON B) (il faut inverser
).
Concrètement, while( chaine1[i] != '\0' && chaine2[i] != '\0' && chaine1[i] == chaine2[i] ) relance une boucle si chaine1[i] != '\0' et chaine2[i] != 0 et chaine1[i] == chaine2[i]. Tu veux continuer la boucle si toutes les conditions sont vraie (donc utilisation de && ).
Du coup, comme il faut que toutes les conditions soient vérifiées pour continuer la boucle, il suffit qu'une seule des conditions ne soit pas vérifiée pour que la boucle ne continue pas, et donc s'arrête.
Donc, la boucle s'arrêtera lorsque qu'une des conditions n'est pas vérifié: ça boucle jusqu'à ce que chaine1[i] == '\0' ou que chaine2[i] == '\0' ou que chaine1[i] == chaine2[i].
En C, le "tant que" n'existe pas, donc il faut penser aux conditions pour que la boucle continue (et pas celles qui la font s'arrêter).
| Citation : (je ne sais plus si c'est considéré comme du ANSI ou du OEM quand on tape directement dans la console ^^) |
Dans une console de type DOS, de 128 à 255, c'est de l'ASCII, et sous windows (en fenêtre).
| Citation : La question est : pourquoi tous ces FF avant ? Surtout que c'est censé être stocké dans un char ! |
En fait, un char (comme un short et un int) est nombre signé. Donc, comme le char est codé sur 1 octet, il va en fait de -127 à 128 inclus. Du coup, si tu prends 0xEE ça code en fait -1, et dans le printf, il y a un cast implicite du char vers l'int, et ça se conde dans l'int: 0xFFFFFFFF.
Pour éviter le problème, il faut le convertir (cast explicite) en "unsigned char" qui est la version non signé (de 0 à 255):
Essaie ceci pour avoir le vrai nombre:
Code :
|
Pour ton exemple, tu as 0x8A qui est en fait 0x8A = 0xFF - 0x76 + 1. Donc, en signé 0x8A = -0x76 = -118 et ça se code en int: -118 = 0xFFFFFFFF - 0x76 + 1 = 0xFFFFFF8A
Je pense avoir compris ton problème:
Code :
|
Que retourne cette fonction sous XP et sous Vista ?
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
| CRicky a écrit :
|
Merci CRicky
La vie ne vaut rien mais rien ne vaut la vie...
Répondre à akred3
Hello =)
| Citation : C'est un problème de logique: si A et B sont des conditions, alors NON(A ET B) <=> (NON A) OU (NON B) (il faut inverser |
Oki, compris. Je supposte que tu voulais dire chaine1[i] != chaine2[i] au lieu de == à la fin de ton avant dernière phrase
| Citation : En fait, un char (comme un short et un int) est nombre signé. Donc, comme le char est codé sur 1 octet, il va en fait de -127 à 128 inclus. Du coup, si tu prends 0xEE ça code en fait -1, et dans le printf, il y a un cast implicite du char vers l'int, et ça se conde dans l'int: 0xFFFFFFFF. |
Tu veux dire 0xFF plutôt pour -1 ? C'est donc le printf qui fait ce cast ? Il suffit donc que je définisse un unsigned char dès le début ?
| Citation : Que retourne cette fonction sous XP et sous Vista ? |
Bah sous Vivi, elle marche très bien.
Et sous XP, je viens de rajouter des return 0 dans le if et else if et là ça semble marcher, bizarre non ???
La fonction retournait automatiquement -1 ou quoi ? et que sous xp en plus ?
Pas très compréhensible tout ça ...
Répondre à XmichouX
| Citation : Oki, compris. Je supposte que tu voulais dire chaine1[i] != chaine2[i] au lieu de == à la fin de ton avant dernière phrase |
Exact
| Citation : Tu veux dire 0xFF plutôt pour -1 ? |
Exact, j'ai voulu faire différent de -1, et j'ai du oublié de changer le reste.
| Citation : C'est donc le printf qui fait ce cast ? Il suffit donc que je définisse un unsigned char dès le début ? |
oui dans le printf, quand tu utilise %d, il attend un entier, donc le compilateur fait un cast implicite.
| Citation : La fonction retournait automatiquement -1 ou quoi ? et que sous xp en plus ? |
Est-ce que tu as utilisé le même exe ou as-tu recompilé sous chacun des OS?
Je pense que c'est la compilation qui s'est faite différemment, avec dans un cas un return 0 par défaut (comme le retour du main dans la norme C99), et dans l'autre un return -1 (pour forcer les erreurs).
Mais normalement, tu as du avoir un warning là-dessus (même si les warning ne font pas partie des normes ANSI).
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hey ,
Ceci dit, pour le %d du printf, d'accord, il attend un entier. Mais à ce moment-là, pourquoi n'affiche-t-il pas un nombre négatif tout simplement au lieu de faire un cast ?
(si c'était un entier, c'est bien ce qu'il ferait pourtant)
Pour l'exe, bah en fait non. J'avais copié/collé mon exe de mon visa à mon xp et ça ne marchait pas.
FInalement, j'ai recompilé sur les deux avec les return 0, et ça marche bien
Tant que j'y suis, j'aurais une petite question par rapport au buffer !
L'autre jour, j'avais manipulé fgets, etc..
J'avais fait cette fonction :
| Citation : void READ (char text[], int size)
|
qui marchait bien au début et qui ne marchait plus ensuite ^^
Le truc pourri là-dedans, c'est que ça faisait un scanf et getch() même si la chaîne était de bonne taille, ce qui est gênant.
Alors j'ai voulu faire un test sur le buffer, pour faire cette étape uniquement si besoin !
Alors j'ai essayé de voir comment tester le buffer :
| Citation : printf ("Stdin vaut %i, %d, %p", *stdin, *stdin, *stdin); |
Le seul truc qui me donnait quelque chose de très cohérent, c'est avec le %d (je comprends pas pourquoi ça donne pas la même chose avec le %d et le %i enfin bref). Par exemple, la chaîne que j'introduis au départ est chaine[10], donc accepte 9 caractères.
Si je tapais lors de ma saisie : baptiste (8 caractères), donc + \n et \0 (\0 rajouté par fgets si j'ai compris ?), le %d m'affichait 0. Si je faisais plus, ça m'affiche toujours quelque chose de très cohérent, par ex :
Là, il m'affiche le 5 et c'est cohérent puisque il reste iste\n (je pensais qu'il y avait encore le \0 après, mais apparemment ou bien ça compte pas comme caractère (puisque c'est null) ou bien c'est rajouté par fgets uniquement pour la chaîne qu'il maitriste). Finalement je me demande si un scanf ("%*[^\0]" ); ne suffirait pas pour vider le buffer !)
Donc je me suis dit qu'à partir de là, je pouvais faire un test sur le buffer en faisant un truc du genre :
if (*stdin != 0)
{
scanf ("%*[^\n]" );
getch();
}
Le problème c'est que ça ne compile pas =D
Mais j'ai cru comprendre sur ce lien : http://www.linux-kheops.com/doc/ma [...] din.3.html que stdin (ou stdout, stderr) était un pointeur de type FILE.. Bref, je sais pas tropcomment faire. =D
Thanks ! GTB
Répondre à XmichouX
Dabord, sur nos machines, pour le printf avec %d ou %x, quand cette fonction lit %d dans la chaine, il s'attend à un entier int en paramètre. Un int étant sur 4 octets, la fonction va bêtement récupérer 4 octets sur la pile. Il faut donc mettre 4 octets sur la pile et pas 1, d'où la conversion.
Ensuite, j'ai mis dans printf(): %02x ce qui indique de mettre 2 caractères (ça ajoute les 0 devant si besoin). Le %x n'est pas signé car il affiche uniquement le code hexadécimal qui lit directement en mémoire. Le signé/non signé, c'est juste de l'interprétation du code hexadécimal.
comme il y a "02" dans le %x, ça affiche 2 caractères, mais si la valeur hexadécimale dépasse les 02, alors ça affiche la valeur complète. Dans ton cas, comme l'entier est négatif, la valeur est 0xFFFFFFxx, ça dépasse les 2 caractères alors il affiche tout.
| Citation : Pour l'exe, bah en fait non. J'avais copié/collé mon exe de mon visa à mon xp et ça ne marchait pas.
|
Intéressant, il faudrait décompiler en assembleur pour voir ce qui se passe.
| Citation : Le seul truc qui me donnait quelque chose de très cohérent, c'est avec le %d (je comprends pas pourquoi ça donne pas la même chose avec le %d et le %i enfin bref). Par exemple, la chaîne que j'introduis au départ est chaine[10], donc accepte 9 caractères. |
Le type FILE est une structure qui contient entre autres le descripteur du fichier, buffer, etc (peut-être dépendant de l'OS).
Bref, de ton côté, tu ne vas jamais lire le contenu d'un FILE.
| Citation : scanf ("%*[^\n]" ); |
C'est quoi ça? un expression régulière? Même si ça fait très longtemps que je n'ai pas utilisé la fonction scanf(), je ne pense pas qu'elle prenne les expression régulières.
stdin, stdout et stderr sont bien des FILE* car ils se manipulent avec des descripteurs de fichier.
Je ne comprends pas très bien ce que tu veux faire dans ta fonction READ.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hello,
D'accord, maintenant, je crois avoir compris pour ce fameux cast ! Je n'avais jamais entendu parler de cette astuce pour voir combien fait un nombre (négatif ) = 0xFF (nombre de bits ...) - 0xle nombre qu'on cherche si bit de poids fort à 1 = le nombre qu'on cherche (en mettant - devant). Ou sinon complément à 2
Pour décompiler, je veux bien, mais je sais pas trop comment faire.. (comment utiliser surtout)
Ok pour le type file. Mais tu vois bien que quand je fais un %d, *stdin, cela m'affiche le nombre de caractères dans le buffer ? (\0 exclu bien-sûr !)
En fait dans ma fonction READ, j'espérais m'en servir pour faire le scanf ("%*[^\n]" ) (supprime les caractères compris dans le buffer sauf les \n suivi d'un getchar(), je me demandais d'ailleurs si on pouvait pas plus simplement faire scanf ("%*[^\n]'). (vu ça sur un tuto de developpez.com). Tu veux le lien ?
Donc en fait j'aurais bien aimé me servir de ce *stdin, mais si c'est pas vraiment possible, tant pis :þ
Répondre à XmichouX
Pour le nombre négatif, tu peux faire un test avec un masque binaire AND: si (val & 0x80 == 0x80), alors négatif.
Pour décompiler, quelle interface de dev utilises-tu? Visual, autre?
pour le stdin, ça dépend de l'implémentation de la structure du FILE que tu utilise. Si dans cette structure, le premier champ (mis à l'adresse de la structure en mémoire) est le nombre de caractère, alors c'est normal que ça t'affiche ça. Le définition de la structure FILE est dans stdio.h, tu peux voir la structure, mais ce n'est pas quelque chose de standard.
Envoie le lien pour scanf.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hey !
| Citation : Dabord, sur nos machines, pour le printf avec %d ou %x, quand cette fonction lit %d dans la chaine, il s'attend à un entier int en paramètre. Un int étant sur 4 octets, la fonction va bêtement récupérer 4 octets sur la pile. Il faut donc mettre 4 octets sur la pile et pas 1, d'où la conversion. |
Juste pour revenir là-dessus ! La conversion en int ne semble que se faire que lorsque le nombre est négatif, c'est bizarre tout de même
J'utilise CodeBlocks comme interface de développement
Je veux bien essayer, ça me ferait une expérience =)
Pour le stdin, tu as une idée pourquoi le résultat est différent selon que je fasse %i et %d (ce qui était la même chose je pensais ! ). Sinon, bien-sûr, je ne connais pas cette structure, donc pas moyen de me servir de mon résultat avec %D pour tester si le buffer est vide ou pas ? Ce serait intéressant tout de même =) Sinon je ne vois pas trop comment tester le buffer, car j'aimerais faire des scanf ou getchar seulement si le buffer est plein (en gros pour pas que ça demande à l'utilisateur de taper quelque chose s'il a bien respecté les règles pour la saisie d'avant).
Pour moi, le problème avec cette fonction (du sdz) :
void viderBuffer()
{
int c = 0;
while (c != '\n' && c != EOF)
{
c = getchar();
}
}
C'est qu'on a au moins un getchar(), ce que je veux éviter. Sinon pour le EOF, je suppose que ça marque la fin : '\0' ? ..
Voici le link pour scanf : http://xrenault.developpez.com/tutoriels/c/scanf/
Répondre à XmichouX
| Citation : Juste pour revenir là-dessus ! La conversion en int ne semble que se faire que lorsque le nombre est négatif, c'est bizarre tout de même |
Non, elle se fait aussi si c'est positif, mais quand c'est positif, en hexa ça s'écrit avec 2 caractères au maximum. Du coup le %02x n'affiche que les 2 caractères hexadécimaux (par exemple 0xFF), mais printf a reçu 0x000000FF.
| Citation : J'utilise CodeBlocks comme interface de développement |
Tu mets un point d'arrêt sur l'accolade de début de ta fonction à problème, et dans le menu debug, tu affiches la fenêtre disassembly, et ça t'affiche le code de la fonction.
| Citation : Sinon, bien-sûr, je ne connais pas cette structure, donc pas moyen de me servir de mon résultat avec %D pour tester si le buffer est vide ou pas ? |
Non, on ne lit pas cette structure.
Tu peux faire fflush(stdin); pour commencer. Tu peux remplacer le getchar() par un fgetc(stdin)
EOF=-1, c'est getchar (et autres get) qui retourne -1 quand la fin est atteinte.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hello !
Voici pour le Debug, je crois que c'est bon (j'ai enlevé les return0 ajoutés évidemment).
Code :
|
Merci pour les autres infos !
PS : J'ai l'impression que je n'arrive à désassembler que cette fonction, pas encore saisi le fonctionnement. Je fais des Debug, et quand il me le permet (pour désassembler) "debug - next instruction"
Message édité par XmichouX le 17-10-2009 à 11:03:14
Répondre à XmichouX
Oui, il ne te désassemble que la fonction dans laquelle tu te trouves.
Les instruction asm sont écrites à l'envers. "mov 0x8(%ebp),%eax" se lit en fait MOV EAX,[EBP+8].
En 004018D7, on voit bien la comparaison (inverse) *caractere >= 97. Si non vérifiée, saute en 004018F4
En 004018DF => comparaison *caractere <= 122. Si non vérifiée, saute en 004018F4
Bloc de 004018E4 à 004018F2 => intérieur du if qui décrémente bien *caractère de 0x20, puis saute en 00401925 pour terminer
En 004018F7 => comparaison *caractere >= 65. Si non vérifiée, saute en 00401906
En 004018FF => comparaison *caractere <= 90. Si non vérifiée, saute en 00401906
Saut direct en 00401925 (le "else if" est vide) pour terminer
Intérieur du "else":
Bloc de 00401906 à 00401917 => appel au printf
En 0040191C => on met -1 en [EBP - 4] (au sommet de la pile), puis saut en 00401925 (saut qui d'ailleurs ne sert à rien puisque c'est l'instruction suivante).
En 00401925, c'est le registre EAX qui sert de valeur de retour, et on y met [EBP - 4].
Donc, si l'on est passé dans le "else", on a -1 dans cette zone mémoire. Si on est passé par le "if" ou le "else if", ça met ce qu'il y avait en mémoire à cet endroit, et vu qu'on n'y a rien mis, ça peut être n'importe quoi. C'est un petit buffer overflow.
Si dans ta fonction, tu regardes en mémoire ce qu'il y avait à cette adresse, tu verrais ce qui est retourné par ta fonction. Un programme compilé en debug ou en optimisé peut donner des résultats différents.
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Hello,
Intéressant cette analyse
| Citation : # 004018D7 cmpb $0x60,(%eax)
|
Ici, par curiosité, où est le supérieur à et inférieur à ?
Ou alors c'est jle et jg qui précisent cette différence
C'est sûrement ça, je me souviens, on en avait parlé. Les différents jump se réfèrent à une valeur précise dans certains bits du registre EFLAGS et jumpent en conséquence.
Faudrait que je lise la doc ASM..
Merci
Répondre à XmichouX
Oui, le "CMP EAX, 60h" compare le contenu du registre EAX avec 60h et allume les flags qui vont bien (zero flag, sign flag...). En fait, il calcule la soustraction (EAX - 60h)
Ensuite, le JLE=Jump if Less or Equal (saut si EAX <= 60h).
Message édité par CRicky le 23-10-2009 à 19:15:19
81F900FA750230EDBADA03ECA80875FBECA808
74FBE4603C0175DFB80300CD10B8004CCD21
Répondre à CRicky
Oki, merci pour toutes ces infos
Répondre à XmichouX
Il y a 2218 utilisateurs connus et inconnus. Pour voir la liste des connectés connus, cliquez ici.
