logo Homepage
Pages: [1]
  Imprimer  
Auteur Fil de discussion: [C] Un débuggage coton sur un petit code  (Lu 4067 fois)
The-Snake

Profil challenge

Classement : 9207/54286

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« le: 17 Octobre 2008 à 15:53:14 »

Bonsoir tout le monde !

Je devais écrire pour hier une fonction qui sépare les mots d'une chaîne passé en paramètres et les stocks dans un tableau (str_to_wordtab) et une autre fonction qui affiche le résultat en séparant chaque mots
A savoir : tout les caractères non-alphanumériques sont considérés comme des séparateurs de mots
Hier soir à 22h30, je teste mon programme avec un main de cette sorte :

int main(int argc, char **argv)
{
  my_show_to_wordtab(my_str_to_wordtab(argv[1]));
  return (0);
}

Et je constate que le programme compilé fonctionne à merveille, et décide de rentrer chez moi !
Cela dit, 23h42, je me prend un zéro aux deux exercices... je fais une petite batterie de test, et me rend compte que mon programme renvoi une segfault pour toutes les chaînes dont le dernier caractère n'est pas alpha-numérique.

Et c'est très rageant. Car j'ai très bien réussi les programmes à finir pour ce soir (qui sont finalement à finir pour lundi soir), mais ils seront comptés comme faux parce que je n'arrive pas à dénicher le bug qui provoque cette segfault...

J'utilise des fonctions tels que my_strlen, car on ne doit utiliser que des fonctions que nous avons codé nous-même, sauf pour write(), malloc() et sizeof().

Le code se divise en six fonctions. Voici d'abord une table des matières, je poste le code juste en bas :
1 - wordtab_iznogood
Vérifie si le caractère envoyé en paramètre est alphanumérique ou non.
Si oui, elle renvoi 1. Sinon 0.

2 - count_words
Compte le nombre de mots dans la chaîne envoyée en paramètre. Pour trouver ou les mots se séparent, elle utilise la fonction wordtqb_iznogood.
En utilisant la variable flag elle supporte ainsi le cas ou deux mots sont séparés par plusieurs espaces ou caractères non-alphanumériques.
Elle renvoi le nombre de mots trouvés.

3. count_char
Compte le nombre de caractères dans un mot.
Au premier caractère séparateur trouvé, elle renvoi le nombre de lettres comptées.

4. my_str_do_wordtab
Elle s'occupe des malloc pour les tableaux contenant les caractères.
Elle inscrit à chaque emplacement la lettre qui lui correspond.
Une fois arrivé à la fin, elle alloue un espace dans le dernier tableau de tableau et écrit simplement 0 pour signifier la fin du tableau.
Elle renvoi le tableau obtenu.

5. my_str_to_wordtab
Elle s'occupe du malloc pour le tableau contenant les tableaux de char.
Elle lance la fonction my_str_do_wordtab une fois la première allocation de mémoire réalisée. Quand le tableau a été rempli avec succès, elle le renvoi.

6. my_show_to_wordtab
Affiche chaque mot contenu dans un tableau sous la forme de celui crée par la fonction my_show_to_wordtab.
Elle utilise les fonctions my_putstr et my_putchar pour afficher le résutlat. my_putchar affiche un caractère, my_putstr une chaine de caractère.

Voici le code des fonctions respectives :
A inclure : stdlib.h , les fonctions my_strlen(), my_putchar() et my_putstr(), les deux premières existent sous la forme strlen et putchar, la troisième je ne sais pas, mais elle est de toute façon très facile à écrire soit-même
Citation de: wordtab_iznogood
int    wordtab_iznogood(char c)
{
  if (c < '0' || c > '9')
    {
      if (c < 'A' || c > 'Z')
    {
      if (c < 'a' || c > 'z')
        return (1);
    }
    }
  return (0);
}
Citation de: count_words
int    count_words(char *str)
{
  int    result;
  int    flag;
  int    cursor;

  cursor = 0;
  result = 0;
  flag = 1;
  while (str[cursor] != '\0')
    {
      if (wordtab_iznogood(str[cursor]) == 1)
      flag = 1;
      else
    {
      if (flag == 1)
        result = result + 1;
      flag = 0;
    }
      cursor = cursor + 1;
    }
  return (result);
}
Citation de: count_chars
int    count_chars(char *str)
{
  int    x;
 
  x = 0;
  while (str[ x ] != '\0')
    {
      if (wordtab_iznogood(str[ x ]) == 1)
    return (x);
      x = x + 1;
    }
  return (x);
}
Citation de: my_str_do_wordtab
char    **my_str_do_wordtab(char *str, char **result, int z, int x)
{
  int    y;

  y = 0;
  while (str[ x ] != '\0')
    {
      if (z < 1)
        result[y] = malloc(sizeof(char) * (count_chars(&str[ x ])));
      if (wordtab_iznogood(str[ x ]) == 1)
        {
          if (z < 1)
            y = y - 1;
          else
            result[y][z] = 0;
          y = y + 1;
          z = -1;
        }
      else
        result[y][z] = str[ x ];
      x = x + 1;
      z = z + 1;
    }
  result[y + 1] = malloc(sizeof(char));
  result[y + 1][0] = 0;
  return (result);
}
Citation de: my_str_to_wordtab
char    **my_str_to_wordtab(char *str)
{
  char    **result;

  result = malloc(sizeof(char*) * count_words(str) + 1);
  result = my_str_do_wordtab(str, result, 0, 0);
  return (result);
}
Citation de: my_show_to_wordtqb
int     my_show_to_wordtab(char **tab)
{
  int   cursor;
  int   nbr_elem;

  cursor = 0;
  nbr_elem = 0;
  while (tab[cursor][0] != 0)
    {
      nbr_elem = nbr_elem + 1;
      cursor = cursor + 1;
    }
  cursor = 0;
  while (cursor < nbr_elem)
    {
      my_putstr(tab[cursor]);
      my_putchar('\n');
      cursor = cursor + 1;
    }
  return (0);
}
« Dernière édition: 17 Octobre 2008 à 15:58:32 par The-Snake » Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54286

Membre Héroïque
*
Hors ligne Hors ligne
Messages: 1258


Voir le profil
« #1 le: 17 Octobre 2008 à 17:34:56 »

Cela dit, 23h42, je me prend un zéro aux deux exercices... je fais une petite batterie de test, et me rend compte que mon programme renvoi une segfault pour toutes les chaînes dont le dernier caractère n'est pas alpha-numérique.

Et il faudrait qu'on déniche ton bug, pour ton devoir, en se paluchant entièrement le code source ? Ah non, pardon, il faut aussi qu'on réimplémente les bouts qui manquent. Hé ben...

On va faire plus simple, hein : tu vas recompiler ton code avec les symboles (-g pour gcc), tu vas reproduire le bug et tu vas dépiauter le core produit. Je veux même bien aider pour la dernière étape, mais seulement si tu fournis au préalable la ligne où le segfault se produit.

Sinon, petits conseils pour produire un code lisible :
1) Utiliser && pour éviter les if en cascades.
2) Ouvrir et fermer un bloc {} dans les if, même si le bloc ne doit contenir qu'une instruction.
3) Éviter les horreurs du genre if (truc=1) machin=1.
4) Nommer correctement les arguments des fonctions et les variables locales (i, j, x, et z, ça va bien pour des compteurs de boucle, et seulement pour ça).
5) Commenter le code (les spécifications en tête de fonction sont indispensables, mais ne remplacent pas les commentaires internes).
6) ...

Oui, je peux paraître très chiant, mais ça fait partie de l'apprentissage de comprendre qu'on n'est pas efficace en développement si le code n'est pas clair et commenté, et si on ne connait pas les principes de base de debugage. Imagine un peu : tu as produit ce code et tu ne comprends pas où est le problème, alors imagine le boulot pour ceux qui découvrent le code et ne sont même pas capables de le recompiler !

Bon courage.

Ps : ce serait vraiment méchant, mais j'aurais pû ajouter que passer le test de pureté n'était peut-être pas la priorité si la production de ce code est urgente.
« Dernière édition: 17 Octobre 2008 à 21:35:42 par _o_ » Journalisée

Les épreuves de hack de NC sont trop faciles ? Et pourtant ! Bienvenue dans la vraie vie : http://thedailywtf.com/Articles/So-You-Hacked-Our-Site!.aspx
The-Snake

Profil challenge

Classement : 9207/54286

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #2 le: 17 Octobre 2008 à 23:51:14 »

Le problème c'est que justement...
J'ai une limite de 80 caractères par lignes (d'où les if (truc) alors if (machin) et pis if (chose)...
Et une limite de 25 lignes par fonction (d'où les accolades en moins... et les paramètres bidons).

C'est assez absurde, mais pour nous faire écrire un code soit-disant lisible, on nous impose des contraintes qui dans certains cas nous font faire de l'illisible.
Si j'avais droit d'attribuer une valeur à certaines variables dès leur déclaration je suis sûr qu'on gagnerait en clarté par exemple...

Citation
Et il faudrait qu'on déniche ton bug, pour ton devoir, en se paluchant entièrement le code source ? Ah non, pardon, il faut aussi qu'on réimplémente les bouts qui manquent. Hé ben...
Les bouts qui manquent ?
Ça m'a l'air plutôt complet et fonctionnel... quand j'essaye d'oublier que dans un cas qui me pourrit la vie ça segfault.

Bon. Ça segfault quand j'essaye de lire le tableau de résultat.
C'est la que ça devient curieux. Pourquoi ça ne segfault QUE dans le cas ou la chaine fini par un caractère non alpha-numérique.
Si j'essaye de lire le contenu de ce tableau à l'intérieur de la fonction my_str_do_wordtab, ça fonctionne... dans tout les cas.
Par contre, si j'essaye de lire à l'extérieur de cette fonction, ça fonctionne dans tout les cas mais pas dans celui ou la chaîne fini par un caractère non alpha-numérique.
Par contre, dès qu'il y a PLUSIEURS caractères non alpha-numériques après la chaine (genre "abcd tati tota +|- ") là ça se remet à marcher...

Curieux non ?
« Dernière édition: 18 Octobre 2008 à 00:35:09 par The-Snake » Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54286

Membre Héroïque
*
Hors ligne Hors ligne
Messages: 1258


Voir le profil
« #3 le: 18 Octobre 2008 à 09:54:07 »

J'ai une limite de 80 caractères par lignes (d'où les if (truc) alors if (machin) et pis if (chose)...
Et une limite de 25 lignes par fonction (d'où les accolades en moins... et les paramètres bidons).

Et ce sont des contraintes tout à fait justifiée en terme lisibilité ! Mais ce qu'il faut comprendre par là, c'est que ce n'est pas pour faire l'économie d'accolades que ce genre de règles existent, mais pour que toute fonction soit simple et lisible. Ce n'est pas un problème d'accolades, mais un problème de conception.
D'autre part,  syntaxiquement, rien n'interdit de mettre les accolades sur les mêmes lignes que les if ou les else (sauf si tu as comme contrainte de respecter une règle d'indentation, cf. http://fr.wikipedia.org/wiki/Style_d'indentation ). De plus, rien n'interdit de mettre des retour-chariots au milieu d'une ligne de C (donc de faire tenir un if sur plusieurs lignes), et d'ajouter des commentaires au milieu.

Un petit exemple :
Code:
if (    ( longueur > 0 && chaine[position] < 'A' )  // Cas droit
     || ( cos(abscisse) - alpha < 0.00001 )  {      // Cas du cercle
  // Faire quelque chose
} else {
  // Faire autre chose
}

Citation
Bon. Ça segfault quand j'essaye de lire le tableau de résultat. [...] Curieux non ?

Non. Je me répète : utilise un débugger et donne la ligne exacte où le segfault se produit.
Ah, et j'ai oublié de le dire hier : compile ton code avec détection des avertissements (-Wall pour gcc) et élimine les avant toute chose.
Journalisée

Les épreuves de hack de NC sont trop faciles ? Et pourtant ! Bienvenue dans la vraie vie : http://thedailywtf.com/Articles/So-You-Hacked-Our-Site!.aspx
The-Snake

Profil challenge

Classement : 9207/54286

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #4 le: 18 Octobre 2008 à 13:03:19 »

Je compile mon code avec la commande :
Citation
gcc -W -Wall -ansi -pedantic *.c
(c'est plutôt une bonne chose ça je suppose, on nous demande de nous démerder pour ne recevoir aucun messages en lançant cette commande).
Je reçoit quand même des erreurs mais rien de bien grâve : une implicit declaration de putchar et le paramètre argc que je n'utilise pas dans le main.

En compilant avec les option g et Wall, je n'ai que la implicit declaration de putchar.
J'ai donc rajouté le prototype de putchar en tête du fichier et je n'ai plus aucun retour de gcc -g -Wall.

Je comprend bien que c'est à cause de ma conception assez médiocre de la chose que je suis obligé de faire des bidouilles et de produire un code tout fratras pour respecter la norme, mais des trois programmes que j'ai écrit pour répondre à la consigne c'est le seul qui fonctionne (enfin... "fonctionne") en respectant la norme.

Citation
D'autre part,  syntaxiquement, rien n'interdit de mettre les accolades sur les mêmes lignes que les if ou les else (sauf si tu as comme contrainte de respecter une règle d'indentation
C'est hors-norme en effet, ça tapera à -3 points par ligne du genre.

La segfault à lieu précisemment à cette ligne :
 while (tab[cursor][0] != 0)

Ça ne m'aide pas à comprendre pourquoi dans  mon programme, "nani nana " n'est pas considéré comme identique à "nani nana  "...

Sinon j'ai trouvé GDB comme débuggeur, mais je comprend pas très bien comment ça fonctionne... ni même qu'est-ce que ça fait. Ça dit à quel ligne du programme ça plante, combien de fois il passe par cette ligne, et de regarder des valeurs de variables à un certain moment si j'ai bien compris l'article Wikipédia.

Mais bref, j'ai découvert d'où viens le bug.
Ça se passe lors de l'écriture du caractère signifiant la fin du tableau de tableaux...
Je ne sais ABSOLUMENT pas pourquoi, mais dans le cas et dans l'UNIQUE cas ou l'avant dernier caractère de la chaîne est alphanumérique et le dernier ne l'est pas, je dois écrire à l'emplacement "y" au lieu de "y + 1"...
Me reste plus qu'à trouver comment faire ça en conservant un maximum de 4 paramètres et 25 lignes par fonctions...
Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54286

Membre Héroïque
*
Hors ligne Hors ligne
Messages: 1258


Voir le profil
« #5 le: 18 Octobre 2008 à 14:13:26 »

(c'est plutôt une bonne chose ça je suppose, on nous demande de nous démerder pour ne recevoir aucun messages en lançant cette commande).

C'est un excellent réflexe, en effet : il m'est arrivé plusieurs fois de traquer un bug vicieux, avant de me rendre compte que son origine m'était signalée par un avertissement du compilateur. Donc, on corrige les avertissements, c'est du temps gagné.

Citation
En compilant avec les option g et Wall, je n'ai que la implicit declaration de putchar.

Note que le -g ne fait pas évoluer les avertissements, il indique juste de conserver les symboles dans le binaire final, ce qui permet ensuite à gdb de retrouver le code source, d'afficher les numéros de lignes et les variables utilisées. Utile en phase de débuggage, il peut être supprimer à la livraison définitive (on peut de façon équivalente «stripper» le binaire).

Citation
Ça ne m'aide pas à comprendre pourquoi dans  mon programme, "nani nana " n'est pas considéré comme identique à "nani nana  "...

Moi non plus. Désolé, mais je n'ai ni le temps, ni la motivation pour essayer de rentrer dans ton code. my_str_do_wordtab() est vraiment trop illisible à mon goût.

En passant, tu as un autre problème, même s'il ne porte pas à conséquence pour cet exercice là : tu n'as aucun free dans ton code. Donc fuite mémoire, puisque tu fais massivement appel à malloc. Dans le cas d'un truc qui doit fonctionner en permanence en recevant des appels extérieurs (typiquement un client/serveur), ce serait catastrophique. Et  les endroits où poser les free() ne sautent pas aux yeux du premier coup d'oeil. Là aussi, c'est à la conception que ce genre de chose doit jouer. Au vue de ton code, je dirais que c'est vraiment de ce côté là que ça pêche. Cela dit, ça ne s'invente pas : c'est en concevant que l'on devient concepteur.

Autre petit problème amusant : tu parcours (au moins) deux fois la chaîne de départ. Une fois pour compter les mots, une deuxième pour découper. Serait-il possible d'optimiser pour ne faire qu'une seule passe¹ ?

Bon courage.

¹: évidemment, la réponse est oui.
Journalisée

Les épreuves de hack de NC sont trop faciles ? Et pourtant ! Bienvenue dans la vraie vie : http://thedailywtf.com/Articles/So-You-Hacked-Our-Site!.aspx
Pages: [1]
  Imprimer  
 
Aller à: