Bug incompréhensible dans un programme C compilé en .EXE
Bug incompréhensible dans un programme C compilé en .EXE
Bonjour à tous,
Je programme en ce moment un peu en C et j'ai depuis quelques temps un problème que je voudrais vous présenter :
Voici Herbert (non je rigole)
Vous pouvez tester, j'ai mis le code source et le programme compilé en pièces jointes.
Tout d'abord, je suis sur Windows 7 et mon compilateur est MinGW.
Mais quel est ce bug ?
En fait, lorsque je programme sur un IDE en ligne (replit), tout se passe normalement quand j'exécute le programme (vous pouvez tester ici).
Alors que quand j'essaie de compiler exactement le même code source avec MinGW pour windows, le programme ne fait pas la même chose lors de l'exécution. (vous pouvez voir sur la capture d'écran la différence).
Je précise encore une chose : Sur windows 10, le bug n'arrive presque jamais.
Quelqu'un pourrait-il m'expliquer d'où vient ce bug et/ou comment le résoudre ?
Merci,
Bobb
Je programme en ce moment un peu en C et j'ai depuis quelques temps un problème que je voudrais vous présenter :
Voici Herbert (non je rigole)
Vous pouvez tester, j'ai mis le code source et le programme compilé en pièces jointes.
Tout d'abord, je suis sur Windows 7 et mon compilateur est MinGW.
Mais quel est ce bug ?
En fait, lorsque je programme sur un IDE en ligne (replit), tout se passe normalement quand j'exécute le programme (vous pouvez tester ici).
Alors que quand j'essaie de compiler exactement le même code source avec MinGW pour windows, le programme ne fait pas la même chose lors de l'exécution. (vous pouvez voir sur la capture d'écran la différence).
Je précise encore une chose : Sur windows 10, le bug n'arrive presque jamais.
Quelqu'un pourrait-il m'expliquer d'où vient ce bug et/ou comment le résoudre ?
Merci,
Bobb
- Fichiers joints
-
- fichiers-source et compilés.zip
- (23.32 Kio) Téléchargé 62 fois
Tous mes programmes sont disponibles ici
↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition PythonJetez un coup d'oeil à mon langage de programmation interprété Neon.
-
BobbProgrammeur
Niveau 10: GR (Guide de Référence)- Messages: 300
- Inscription: 19 Avr 2020, 12:37
- Localisation: Morbihan
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: CPGE MPI
Re: Bug incompréhensible dans un programme C compilé en .EXE
Salut Bobb,
j'ai juste survolé ton code, et pour être franc, sans que tu le prennes mal bien sûr, celui-ci me semble éminemment complexe compte tenu de ce que tu cherches à faire.
Je note plusieurs points qui de mon humble point de vue posent problème.
1) il y a de multiples appels à des fonctions "malloc" pour allouer dynamiquement de la mémoire aux chaines de caractères que tu manipules, mais je n'ai vu (sauf erreur) aucune libération de mémoire via des appels correspondants à "free". En gros, cela signifie fuites de mémoires à chaque appel. Je te rappelle que le C n'a pas de garbage collector, donc ce qui est alloué à un moment et non restitué est perdu jusqu'au prochain reboot (ou plantage).
Par exemple, ta fonction input( )
à chaque utilisation de la fonction, la variable var se voit allouer 100*char qui ne sont jamais libérés.
De meme ta fonction retourne newVar dont la mémoire a été allouée, mais qu'il te faudra à un moment ou à un autre libérer.
2) je note aussi des trucs bizarres, dont par exemple cette ligne :
3) évite les imbrications de fonctions qui rendent le code illisible et intraçable : par exemple
Donc pour répondre à ta question et sans trop m'avancer, ton bug est typiquement lié à un problème de gestion/mauvaise utilisation mémoire. Comme tu travailles avec des chaines, selon la tolérance du système/compilateur, ca passe ou ca ne passe pas. Mais à mon sens ton code est beaucoup trop complexe.
Je ne peux que te conseiller de le reprendre complètement en le décomposant et en vérifiant avec un débugger si c'est Ok ou pas via des watches/breakpoints. Code::Blocks avec MinGW devrait t'être d'une grande aide.
A plus
Sly
j'ai juste survolé ton code, et pour être franc, sans que tu le prennes mal bien sûr, celui-ci me semble éminemment complexe compte tenu de ce que tu cherches à faire.
Je note plusieurs points qui de mon humble point de vue posent problème.
1) il y a de multiples appels à des fonctions "malloc" pour allouer dynamiquement de la mémoire aux chaines de caractères que tu manipules, mais je n'ai vu (sauf erreur) aucune libération de mémoire via des appels correspondants à "free". En gros, cela signifie fuites de mémoires à chaque appel. Je te rappelle que le C n'a pas de garbage collector, donc ce qui est alloué à un moment et non restitué est perdu jusqu'au prochain reboot (ou plantage).
Par exemple, ta fonction input( )
- Code: Tout sélectionner
char* input(char *text) {// fonction pour demander le calcul
char *var=malloc(100*sizeof(char));
printf("%s",text);
scanf("%s",var);
char* newVar = malloc(sizeof(char)*strlen(var));
strcpy(newVar,var);
return newVar;
}
à chaque utilisation de la fonction, la variable var se voit allouer 100*char qui ne sont jamais libérés.
De meme ta fonction retourne newVar dont la mémoire a été allouée, mais qu'il te faudra à un moment ou à un autre libérer.
2) je note aussi des trucs bizarres, dont par exemple cette ligne :
- Code: Tout sélectionner
char** operateur=malloc(sizeof(char*)*6);
- Code: Tout sélectionner
operateur[0]="**";
3) évite les imbrications de fonctions qui rendent le code illisible et intraçable : par exemple
- Code: Tout sélectionner
calcul=calc(input("Votre calcul : "));
Donc pour répondre à ta question et sans trop m'avancer, ton bug est typiquement lié à un problème de gestion/mauvaise utilisation mémoire. Comme tu travailles avec des chaines, selon la tolérance du système/compilateur, ca passe ou ca ne passe pas. Mais à mon sens ton code est beaucoup trop complexe.
Je ne peux que te conseiller de le reprendre complètement en le décomposant et en vérifiant avec un débugger si c'est Ok ou pas via des watches/breakpoints. Code::Blocks avec MinGW devrait t'être d'une grande aide.
A plus
Sly
Dernière édition par SlyVTT le 25 Mai 2021, 19:16, édité 1 fois.
Some works in progress :
The GUI Toolkit NF for nSpire | MyShmup for fxCG-50 | Magic Light for Casio Graph 90+E and Magic Light for nSpire CX/CX-II | Simple Text Editor for nSpire | OutRun for Casio Graph 90+E |
And more to come ... stay tuned
-
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)- Messages: 482
- Images: 31
- Inscription: 19 Jan 2021, 09:41
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- GitHub: SlyVTT
Re: Bug incompréhensible dans un programme C compilé en .EXE
Bobb,
j'oubliais le plus important, le C est vraiment complexe et demande beaucoup de rigueur, il ne faut surtout pas de décourager, il est normal de galérer au début.
Un autre petit conseil, mais peut être fais tu déjà : fait tes algo sur papier avant pour bien réfléchir a comment ton code doit se décomposer.
Dans ton cas :
1) lire l'opération demandée par l'utilisateur et stocker dans "Entrée"
2) décomposer "Entrée" pour trouver l'operation demandée (opérateur) et les opérandes ("nombre1" et "nombre2")
3) pour trouver l'opérateur : scanner "entrée" pour chercher les symboles + - * / ** ...
4) pour trouver les opérandes : scanner "entrée" pour chercher les chiffres et eventuellement le signe - (attention aux cas particuliers par exemple si nombre 2 est négatif ...)
5) convertir les chaines "nombre1" et "nombre2" en valeurs numérique correspondantes
6) conduire l'operation demandée en focntion de l'opérateur trouvé
7) afficher le résultat
Bon, j'ai fait rapidos, c'est peut être à raffiner un peu, mais c'est le principe et ensuite il faut habiller chacune des fonctions
Ciao
Sly
j'oubliais le plus important, le C est vraiment complexe et demande beaucoup de rigueur, il ne faut surtout pas de décourager, il est normal de galérer au début.
Un autre petit conseil, mais peut être fais tu déjà : fait tes algo sur papier avant pour bien réfléchir a comment ton code doit se décomposer.
Dans ton cas :
1) lire l'opération demandée par l'utilisateur et stocker dans "Entrée"
2) décomposer "Entrée" pour trouver l'operation demandée (opérateur) et les opérandes ("nombre1" et "nombre2")
3) pour trouver l'opérateur : scanner "entrée" pour chercher les symboles + - * / ** ...
4) pour trouver les opérandes : scanner "entrée" pour chercher les chiffres et eventuellement le signe - (attention aux cas particuliers par exemple si nombre 2 est négatif ...)
5) convertir les chaines "nombre1" et "nombre2" en valeurs numérique correspondantes
6) conduire l'operation demandée en focntion de l'opérateur trouvé
7) afficher le résultat
Bon, j'ai fait rapidos, c'est peut être à raffiner un peu, mais c'est le principe et ensuite il faut habiller chacune des fonctions
Ciao
Sly
Some works in progress :
The GUI Toolkit NF for nSpire | MyShmup for fxCG-50 | Magic Light for Casio Graph 90+E and Magic Light for nSpire CX/CX-II | Simple Text Editor for nSpire | OutRun for Casio Graph 90+E |
And more to come ... stay tuned
-
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)- Messages: 482
- Images: 31
- Inscription: 19 Jan 2021, 09:41
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- GitHub: SlyVTT
Re: Bug incompréhensible dans un programme C compilé en .EXE
Merci pour toutes ces précisions éclairantes, je comprends bien que mon code est "sale".
Je dois le nettoyer et régler TOUS mes bugs avant d'aller plus loin.
Reprendre mon code de zéro pourrait être aussi une idée. Et là je pourrais le faire proprement avec découpage en lexèmes, arbre syntaxique etc.
Sinon pour le tableau de pointeurs de char*, c'est parce que la fonction qui prend ça en paramètres peut prendre plusieurs opérateurs qui sont composés de plusieurs caractères.
Par rapport au fait que j'utilise plein de mémoire et que je ne la libère pas, j'avais une question.
Normalement, tous les pointeurs créés dans une fonction se suppriment à la fin de la fonction. Donc je ne suis pas obligé d'utiliser free juste avant la fin d'une fonction ?
Bobb
Je dois le nettoyer et régler TOUS mes bugs avant d'aller plus loin.
Reprendre mon code de zéro pourrait être aussi une idée. Et là je pourrais le faire proprement avec découpage en lexèmes, arbre syntaxique etc.
Sinon pour le tableau de pointeurs de char*, c'est parce que la fonction qui prend ça en paramètres peut prendre plusieurs opérateurs qui sont composés de plusieurs caractères.
Par rapport au fait que j'utilise plein de mémoire et que je ne la libère pas, j'avais une question.
Normalement, tous les pointeurs créés dans une fonction se suppriment à la fin de la fonction. Donc je ne suis pas obligé d'utiliser free juste avant la fin d'une fonction ?
Bobb
Tous mes programmes sont disponibles ici
↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition PythonJetez un coup d'oeil à mon langage de programmation interprété Neon.
-
BobbProgrammeur
Niveau 10: GR (Guide de Référence)- Messages: 300
- Inscription: 19 Avr 2020, 12:37
- Localisation: Morbihan
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: CPGE MPI
Re: Bug incompréhensible dans un programme C compilé en .EXE
Salut Bobb,
pour le char**, ok, j'ai survolé, mais je te confirme que dans ce cas, si tu veux avoir un code propre, il faut dans l'ordre :
1/ dire que ton tableau fait X entrée (le nombre d'opérateur à considérer (par exemple si tu as 6 opérateurs alors malloc(size_of(char*)*6);
2/ à ce stade, tu as réservé 6 emplacements pour des (char*) que tu sais pointer avec *operateur[0] à [5]
3/ désormais, il faut pour chacune de 6 chaines, allouer la mémoire pour le nombre de caractères via un malloc(size_of(char) * nbcaractere ); sinon tu inscris dans une mémoire non allouée et c'est le risque de corruption mémoire associée et plantage.
Ensuite pour entrer l'opérateur, soit tu as un seul caractère, par exemple '+' et là tu peux faire un operateur[0][0]='+'; par exemple pour le premier opérateur, ou operateur[2][0]='-' pour le 3eme operateur (car il s'agit bien d'un double tableau de char).
Si c'est un operateur avec plusieurs caractères, par exemple "**" ou bien par exemple plus tard "sin" ou "cos", alors il faut passer par un strcpy(operateur[0], "**" );
Pour la liberation de la mémoire, la portée des variable telle que tu décris est vraie seulement pour les variables statiques. Pour les variables dynamiques avec allocation via un pointeur, la mémoire allouée via un malloc reste allouée=occupée, par contre le pointeur lui est détruit, tu te retrouves donc avec impossibilité de dés-allouer cette mémoire, d'où une fuite. C'est un peu l'image d'un ballon de baudruche. Le pointeur correspond à la ficelle que tu tiens dans la main. Le Malloc sert à gonfler le ballon pour le remplir et free sert à le dégonfler pour récupérer l'air. Si tu coupes la ficelle, le ballon s'envole et tu ne peux plus récupérer l'air, donc il est perdu. Donc oui la portée du pointeur est la fonction, par contre c'est faux pour la mémoire allouée et c'est pour cela qu'il faut toujours bien garder une référence (=un pointeur) sur la mémoire allouée pour pouvoir la libérer ensuite.
J'espère avec été clair dans mes explications.
A plus
Sylvain
pour le char**, ok, j'ai survolé, mais je te confirme que dans ce cas, si tu veux avoir un code propre, il faut dans l'ordre :
1/ dire que ton tableau fait X entrée (le nombre d'opérateur à considérer (par exemple si tu as 6 opérateurs alors malloc(size_of(char*)*6);
2/ à ce stade, tu as réservé 6 emplacements pour des (char*) que tu sais pointer avec *operateur[0] à [5]
3/ désormais, il faut pour chacune de 6 chaines, allouer la mémoire pour le nombre de caractères via un malloc(size_of(char) * nbcaractere ); sinon tu inscris dans une mémoire non allouée et c'est le risque de corruption mémoire associée et plantage.
Ensuite pour entrer l'opérateur, soit tu as un seul caractère, par exemple '+' et là tu peux faire un operateur[0][0]='+'; par exemple pour le premier opérateur, ou operateur[2][0]='-' pour le 3eme operateur (car il s'agit bien d'un double tableau de char).
Si c'est un operateur avec plusieurs caractères, par exemple "**" ou bien par exemple plus tard "sin" ou "cos", alors il faut passer par un strcpy(operateur[0], "**" );
Pour la liberation de la mémoire, la portée des variable telle que tu décris est vraie seulement pour les variables statiques. Pour les variables dynamiques avec allocation via un pointeur, la mémoire allouée via un malloc reste allouée=occupée, par contre le pointeur lui est détruit, tu te retrouves donc avec impossibilité de dés-allouer cette mémoire, d'où une fuite. C'est un peu l'image d'un ballon de baudruche. Le pointeur correspond à la ficelle que tu tiens dans la main. Le Malloc sert à gonfler le ballon pour le remplir et free sert à le dégonfler pour récupérer l'air. Si tu coupes la ficelle, le ballon s'envole et tu ne peux plus récupérer l'air, donc il est perdu. Donc oui la portée du pointeur est la fonction, par contre c'est faux pour la mémoire allouée et c'est pour cela qu'il faut toujours bien garder une référence (=un pointeur) sur la mémoire allouée pour pouvoir la libérer ensuite.
J'espère avec été clair dans mes explications.
A plus
Sylvain
Dernière édition par SlyVTT le 25 Mai 2021, 16:02, édité 1 fois.
Some works in progress :
The GUI Toolkit NF for nSpire | MyShmup for fxCG-50 | Magic Light for Casio Graph 90+E and Magic Light for nSpire CX/CX-II | Simple Text Editor for nSpire | OutRun for Casio Graph 90+E |
And more to come ... stay tuned
-
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)- Messages: 482
- Images: 31
- Inscription: 19 Jan 2021, 09:41
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- GitHub: SlyVTT
Re: Bug incompréhensible dans un programme C compilé en .EXE
Merci, je comprends mieux les raisons possibles de mes bugs, et je corrigerai tout ça.
Tous mes programmes sont disponibles ici
↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition PythonJetez un coup d'oeil à mon langage de programmation interprété Neon.
-
BobbProgrammeur
Niveau 10: GR (Guide de Référence)- Messages: 300
- Inscription: 19 Avr 2020, 12:37
- Localisation: Morbihan
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: CPGE MPI
Re: Bug incompréhensible dans un programme C compilé en .EXE
Juste pour te confirmer que c'est un truc pas forcément évident, dans le GUI Toolkit, j'ai eu récemment des problèmes de perte de pointeurs et donc pour libérer la mémoire, j'ai donc du récrire tout un pan de mon code (quelques milliers de ligne pour le passer en vrai C++ et virer mes (char*) pour les remplacer par des std::string). L'avantage est que ces objets sont nettement plus évolués et contiennent un destructeur qui simplifie les choses.
Au moins je ne referai plus cette bétise. C'est en forgeant qu'on devient forgeron comme dit l'adage ...
Donc tu vois, même quand on à l'habitude, parfois on fait des âneries et on galère
A plus
Sly
Au moins je ne referai plus cette bétise. C'est en forgeant qu'on devient forgeron comme dit l'adage ...
Donc tu vois, même quand on à l'habitude, parfois on fait des âneries et on galère
A plus
Sly
Some works in progress :
The GUI Toolkit NF for nSpire | MyShmup for fxCG-50 | Magic Light for Casio Graph 90+E and Magic Light for nSpire CX/CX-II | Simple Text Editor for nSpire | OutRun for Casio Graph 90+E |
And more to come ... stay tuned
-
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)- Messages: 482
- Images: 31
- Inscription: 19 Jan 2021, 09:41
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- GitHub: SlyVTT
Re: Bug incompréhensible dans un programme C compilé en .EXE
Suggestions basées sur l'extrait posté dans le premier post de SlyVTT:
* la première étape est de mettre une longueur dans la chaîne de formattage des fonctions de la famille scanf, c'est à dire utiliser "%100s" avec une chaîne de 101 caractères, sinon des buffer overflows sont possibles - c'est une vulnérabilité;
* la seconde étape est d'utiliser une forme ou une autre de constante, de préférence non matérialisée (#define va bien) pour la taille du buffer temporaire, et d'utiliser l'astuce de stringification basée sur le préprocesseur, utilisant une paire de macros dont la macro supérieure qu'on appelle est souvent nommée "xstr", permettant d'écrire une construction de la forme
* la première étape est de mettre une longueur dans la chaîne de formattage des fonctions de la famille scanf, c'est à dire utiliser "%100s" avec une chaîne de 101 caractères, sinon des buffer overflows sont possibles - c'est une vulnérabilité;
* la seconde étape est d'utiliser une forme ou une autre de constante, de préférence non matérialisée (#define va bien) pour la taille du buffer temporaire, et d'utiliser l'astuce de stringification basée sur le préprocesseur, utilisant une paire de macros dont la macro supérieure qu'on appelle est souvent nommée "xstr", permettant d'écrire une construction de la forme
- Code: Tout sélectionner
scanf("%" xstr(MA_JOLIE_LONGUEUR_TABLEAU_TEMPORAIRE) "%s", var);
Membre de la TI-Chess Team.
Co-mainteneur de GCC4TI (documentation en ligne de GCC4TI), TIEmu et TILP.
Co-mainteneur de GCC4TI (documentation en ligne de GCC4TI), TIEmu et TILP.
-
Lionel DebrouxSuper Modo
Niveau 14: CI (Calculateur de l'Infini)- Messages: 6859
- Inscription: 23 Déc 2009, 00:00
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: -
- GitHub: debrouxl
Re: Bug incompréhensible dans un programme C compilé en .EXE
Je te remercie de ta réponse, j'essaierai de la comprendre et je l'utiliserais.
J'ai commencé à mettre des free() pour vider mes pointeurs, et je me posais une question. Dans ma fonction, j'alloue un pointeur avec malloc, et je le retourne. Comment puis-je faire pour vider le pointeur après l'avoir retourné ?
J'ai commencé à mettre des free() pour vider mes pointeurs, et je me posais une question. Dans ma fonction, j'alloue un pointeur avec malloc, et je le retourne. Comment puis-je faire pour vider le pointeur après l'avoir retourné ?
Tous mes programmes sont disponibles ici
↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition PythonJetez un coup d'oeil à mon langage de programmation interprété Neon.
-
BobbProgrammeur
Niveau 10: GR (Guide de Référence)- Messages: 300
- Inscription: 19 Avr 2020, 12:37
- Localisation: Morbihan
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: CPGE MPI
Re: Bug incompréhensible dans un programme C compilé en .EXE
Pour vider le pointeur, il faut le mettre à nullptr.
Pour vider la mémoire pointée, memset() avant free(). Sur gros ordinateur, il y a des allocateurs mémoire qui font ce memset en interne dans les fonctions type malloc() et les fonctions type free() si on le leur demande, par exemple celui de la glibc quand la variable d'environnement MALLOC_PERTURB_ est définie, ou sous MSVC, en mode debug.
La paire
A toi de voir si c'est plus clair ainsi.
Pour vider la mémoire pointée, memset() avant free(). Sur gros ordinateur, il y a des allocateurs mémoire qui font ce memset en interne dans les fonctions type malloc() et les fonctions type free() si on le leur demande, par exemple celui de la glibc quand la variable d'environnement MALLOC_PERTURB_ est définie, ou sous MSVC, en mode debug.
- Code: Tout sélectionner
void * truc = malloc(SIZE);
return truc;
// nullptr est C++11 et ultérieurs; en C et en C++ vieux style, c'est NULL.
// A mon sens, mieux vaut arrêter d'utiliser du C pour du nouveau code. Même si on n'utilise pas les nombreuses et très importantes features C++ dont le C ne bénéficiera jamais, rien que le typage plus fort est un avantage pour réduire les bêtises qu'on peut écrire.
if (nullptr != truc) {
// faire quelque chose avec truc, et quand on a fini...
free(truc); truc = nullptr; // Toujours faire les deux, sauf si on réutilise la variable plus loin pour pointer vers autre chose.
}
else {
// On arrête car on n'a pas pu allouer la mémoire.
}
La paire
- Code: Tout sélectionner
free(truc); truc = nullptr;
- Code: Tout sélectionner
inline void myfree(void ** ptr) {
free(*ptr);
*ptr = nullptr;
}
// Utilisation: myfree(&truc);
A toi de voir si c'est plus clair ainsi.
Membre de la TI-Chess Team.
Co-mainteneur de GCC4TI (documentation en ligne de GCC4TI), TIEmu et TILP.
Co-mainteneur de GCC4TI (documentation en ligne de GCC4TI), TIEmu et TILP.
-
Lionel DebrouxSuper Modo
Niveau 14: CI (Calculateur de l'Infini)- Messages: 6859
- Inscription: 23 Déc 2009, 00:00
- Localisation: France
- Genre:
- Calculatrice(s):→ MyCalcs profile
- Classe: -
- GitHub: debrouxl
16 messages
• Page 1 sur 2 • 1, 2
Retourner vers Langages alternatifs
Qui est en ligne
Utilisateurs parcourant ce forum: Aucun utilisateur enregistré et 12 invités