π
<-
Chat plein-écran
[^]

Bug incompréhensible dans un programme C compilé en .EXE

Assembleur, Axe, C/C++, ICE...

Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Bobb » 25 Mai 2021, 10:47

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).

Image

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 Python
Jetez un coup d'oeil à mon langage de programmation interprété Neon.

Image
Avatar de l’utilisateur
BobbProgrammeur
Niveau 10: GR (Guide de Référence)
Niveau 10: GR (Guide de Référence)
Prochain niv.: 94.5%
 
Messages: 300
Inscription: 19 Avr 2020, 12:37
Localisation: Morbihan
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: CPGE MPI

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude SlyVTT » 25 Mai 2021, 12:29

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( )
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);
qui ne me semble vraiment pas très à propos (nous sommes à deux niveaux de pointeurs). Tout cela par la suite pour "remplir" la chaine par
Code: Tout sélectionner
operateur[0]="**";
D'une manière générale, un niveau de pointeur est suffisant pour le commun des mortels et au-delà, il faut passer en mode alerte, c'est qu'il y a un truc à simplifier. (je te passe le fait que assigner de force une chaine de 2 caractères dans un char me fait tousser ;-) et que en plus operateur[0] ne correspond pas à ce que tu penses que cela correspond, en fait operateur[0] correspond à un (char*), pas à un (char)). Bref : simplifie et vérifie.

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 : "));
serait nettement plus maintenable en décomposant les actions : on fait la lecture de l'entrée utilisateur, on stocke (comme ça si besoin on peut vérifier que c'est Ok avec un débugger), et ensuite on fait des opérations sur cette entrée utilisateur.

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 nSpireMyShmup for fxCG-50Magic Light for Casio Graph 90+E
and
Magic Light for nSpire CX/CX-II
Simple Text Editor for nSpireOutRun for Casio Graph 90+E
95%
50%
100%
75%
100%
And more to come ... stay tuned
Avatar de l’utilisateur
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Prochain niv.: 43%
 
Messages: 482
Images: 31
Inscription: 19 Jan 2021, 09:41
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
GitHub: SlyVTT

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude SlyVTT » 25 Mai 2021, 12:42

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
Some works in progress :
The GUI Toolkit NF for nSpireMyShmup for fxCG-50Magic Light for Casio Graph 90+E
and
Magic Light for nSpire CX/CX-II
Simple Text Editor for nSpireOutRun for Casio Graph 90+E
95%
50%
100%
75%
100%
And more to come ... stay tuned
Avatar de l’utilisateur
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Prochain niv.: 43%
 
Messages: 482
Images: 31
Inscription: 19 Jan 2021, 09:41
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
GitHub: SlyVTT

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Bobb » 25 Mai 2021, 13:24

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

Tous mes programmes sont disponibles ici

↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition Python
Jetez un coup d'oeil à mon langage de programmation interprété Neon.

Image
Avatar de l’utilisateur
BobbProgrammeur
Niveau 10: GR (Guide de Référence)
Niveau 10: GR (Guide de Référence)
Prochain niv.: 94.5%
 
Messages: 300
Inscription: 19 Avr 2020, 12:37
Localisation: Morbihan
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: CPGE MPI

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude SlyVTT » 25 Mai 2021, 14:59

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
Dernière édition par SlyVTT le 25 Mai 2021, 16:02, édité 1 fois.
Some works in progress :
The GUI Toolkit NF for nSpireMyShmup for fxCG-50Magic Light for Casio Graph 90+E
and
Magic Light for nSpire CX/CX-II
Simple Text Editor for nSpireOutRun for Casio Graph 90+E
95%
50%
100%
75%
100%
And more to come ... stay tuned
Avatar de l’utilisateur
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Prochain niv.: 43%
 
Messages: 482
Images: 31
Inscription: 19 Jan 2021, 09:41
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
GitHub: SlyVTT

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Bobb » 25 Mai 2021, 15:21

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 Python
Jetez un coup d'oeil à mon langage de programmation interprété Neon.

Image
Avatar de l’utilisateur
BobbProgrammeur
Niveau 10: GR (Guide de Référence)
Niveau 10: GR (Guide de Référence)
Prochain niv.: 94.5%
 
Messages: 300
Inscription: 19 Avr 2020, 12:37
Localisation: Morbihan
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: CPGE MPI

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude SlyVTT » 25 Mai 2021, 16:06

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
Some works in progress :
The GUI Toolkit NF for nSpireMyShmup for fxCG-50Magic Light for Casio Graph 90+E
and
Magic Light for nSpire CX/CX-II
Simple Text Editor for nSpireOutRun for Casio Graph 90+E
95%
50%
100%
75%
100%
And more to come ... stay tuned
Avatar de l’utilisateur
SlyVTTPremium
Niveau 12: CP (Calculatrice sur Pattes)
Niveau 12: CP (Calculatrice sur Pattes)
Prochain niv.: 43%
 
Messages: 482
Images: 31
Inscription: 19 Jan 2021, 09:41
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
GitHub: SlyVTT

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Lionel Debroux » 25 Mai 2021, 19:04

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
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.
Avatar de l’utilisateur
Lionel DebrouxSuper Modo
Niveau 14: CI (Calculateur de l'Infini)
Niveau 14: CI (Calculateur de l'Infini)
Prochain niv.: 11.2%
 
Messages: 6859
Inscription: 23 Déc 2009, 00:00
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: -
GitHub: debrouxl

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Bobb » 02 Juin 2021, 08:01

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é ?

Tous mes programmes sont disponibles ici

↳ Testez mon simulateur Android sur Ti-83 Premium CE et / ou Édition Python
Jetez un coup d'oeil à mon langage de programmation interprété Neon.

Image
Avatar de l’utilisateur
BobbProgrammeur
Niveau 10: GR (Guide de Référence)
Niveau 10: GR (Guide de Référence)
Prochain niv.: 94.5%
 
Messages: 300
Inscription: 19 Avr 2020, 12:37
Localisation: Morbihan
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: CPGE MPI

Re: Bug incompréhensible dans un programme C compilé en .EXE

Message non lude Lionel Debroux » 02 Juin 2021, 08:20

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.

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;
peut être wrappée sous forme de fonction inline globale à mettre dans un header commun, du genre
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.
Avatar de l’utilisateur
Lionel DebrouxSuper Modo
Niveau 14: CI (Calculateur de l'Infini)
Niveau 14: CI (Calculateur de l'Infini)
Prochain niv.: 11.2%
 
Messages: 6859
Inscription: 23 Déc 2009, 00:00
Localisation: France
Genre: Homme
Calculatrice(s):
MyCalcs profile
Classe: -
GitHub: debrouxl

Suivante

Retourner vers Langages alternatifs

Qui est en ligne

Utilisateurs parcourant ce forum: Aucun utilisateur enregistré et 9 invités

-
Rechercher
-
Social TI-Planet
-
Sujets à la une
Comparaisons des meilleurs prix pour acheter sa calculatrice !
Aidez la communauté à documenter les révisions matérielles en listant vos calculatrices graphiques !
Phi NumWorks jailbreak
123
-
Faire un don / Premium
Pour plus de concours, de lots, de tests, nous aider à payer le serveur et les domaines...
Faire un don
Découvrez les avantages d'un compte donateur !
JoinRejoignez the donors and/or premium!les donateurs et/ou premium !


Partenaires et pub
Notre partenaire Jarrety Calculatrices à acheter chez Calcuso
-
Stats.
924 utilisateurs:
>910 invités
>10 membres
>4 robots
Record simultané (sur 6 mois):
6892 utilisateurs (le 07/06/2017)
-
Autres sites intéressants
Texas Instruments Education
Global | France
 (English / Français)
Banque de programmes TI
ticalc.org
 (English)
La communauté TI-82
tout82.free.fr
 (Français)