Page 1 sur 2

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

Message non luPosté: 25 Mai 2021, 10:47
de Bobb
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

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

Message non luPosté: 25 Mai 2021, 12:29
de SlyVTT
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

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

Message non luPosté: 25 Mai 2021, 12:42
de SlyVTT
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

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

Message non luPosté: 25 Mai 2021, 13:24
de Bobb
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

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

Message non luPosté: 25 Mai 2021, 14:59
de SlyVTT
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

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

Message non luPosté: 25 Mai 2021, 15:21
de Bobb
Merci, je comprends mieux les raisons possibles de mes bugs, et je corrigerai tout ça.

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

Message non luPosté: 25 Mai 2021, 16:06
de SlyVTT
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

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

Message non luPosté: 25 Mai 2021, 19:04
de Lionel Debroux
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);

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

Message non luPosté: 02 Juin 2021, 08:01
de Bobb
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é ?

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

Message non luPosté: 02 Juin 2021, 08:20
de Lionel Debroux
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.