logo Homepage
Pages: [1]
  Imprimer  
Auteur Fil de discussion: [C] Une bizarrerie sur des bouts de code  (Lu 6483 fois)
The-Snake

Profil challenge

Classement : 9207/54285

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« le: 26 Novembre 2008 à 20:46:30 »

Voilà, j'ai pondu un **No Sms** boût de code et je me retrouve avec une segfault... assez bizarre, voyez plutôt !

Citation
void    malloc_var_tab(t_var **var_tab, int foo)
{
  *var_tab = malloc(sizeof(t_var) * foo);
  if (!*var_tab)
    {
      my_printf("my_printf: malloc pwned you: malloc_var_tab\n");
      exit (-1);
    }
  foo = foo - 1;
  while (foo >= 0)
    {
      var_tab[foo]->type = 0;
      foo = foo - 1;
    }
}

Voici t_var pour les curieux
Citation
typedef struct  s_var
{
  short         type;
  char          *str;
  int           nbr;
}               t_var;

Alors maintenant, à moins que vous soyez infiniment plus intelligent que moi vous vous dites "bah c'est bien c'est cool".
Eh bah NON, c'est pas cool !

C'est une petite curiosité tout de même...
En gros, à partir du moment ou la variable "foo" vaut plus de 4 au début de la fonction, le programme va segfaulter dans la boucle, en essayant d'écrire au troisième emplacement du tableau.
Mais si ce n'était que ça ! Je ne peux certes pas écrire dans  var_tab[3]... mais je peux écrire dans  var_tab[4], var_tab[5], etc jusqu'à la fin du tableau.

Etrange vous trouvez pas ?
Je me demandait comment ce genre de choses pouvait bien arriver, d'autant plus que ce n'est pas la première fois que j'ai à faire à ça. Genre j'ai aussi une petite saleté de ce genre, admirez :

Citation
strcpy(sbase, base);
putchar(sbase[2]);
putchar('\n');
while (cursor < 5)
  tab[cursor] = my_getnbr_base(av[cursor++ + 3], base);
putchar(sbase[2]);
putchar('\n');
(et oui, le while bidouillé à la place d'un for, c'est moche, mais voilà !)
En gros, le premier putchar m'affiche le contenu de sbase[2], c'est à dire '2'.
Le deuxième putchar m'affiche le contenu de sbase[2], c'est à dire rien. Après le while, base et sbase deviennent totalement vide. J'ai vérifié les adresses : ça pointe toujours au même endroit... c'est juste qu'il n'y a plus rien dedans, ni dans base, ni dans sbase (sbase qui n'a pourtant pas été appelée dans le while, ça n'a donc bien aucun sens on est d'accord ?).

Enfin je commence à être fatigué de ces saletés, et j'aimerais bien avoir l'avis d'un sage qui saurait qu'est-ce qui provoque ces bizarreries de la mémoire ?
« Dernière édition: 28 Novembre 2008 à 11:15:45 par the lsd » Journalisée
sirk390
Beta testeur

Profil challenge

Classement : 1/54285

Membre Junior
*
Hors ligne Hors ligne
Messages: 92

Sirk390


Voir le profil WWW
« #1 le: 26 Novembre 2008 à 21:22:15 »

Citation
  var_tab[foo]->type = 0;
Ici tu utilise la valeur non initialisé qui est dans la case var_tab[foo] du tableau pour définir à quelle adresse tu va écrire. Ensuite tu écris 0 à de cette adresse là ... Bref, tu écris à un endroit plus ou moins random de la mémoire, donc ca plante plus ou moins en random.
Il te faudrait surement un boucle qui fait les mallocs pour tous les objets t_var que tu veux stoquer.
Attentions aux pointeurs sur pointeurs... c'est facile de se planter.

Pour ton deuxième problème je ne peux pas faire grand chose car il manque la moitié du code.

Journalisée

Trop cool NC!
_o_
Relecteur

Profil challenge

Classement : 42/54285

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


Voir le profil
« #2 le: 26 Novembre 2008 à 21:28:49 »

Etrange vous  trouvez pas ?

Pas vraiment, non. Dans ta tête, var_tab[foo], ça pointe sur quelque chose ? Pour t'en assurer, tu devrais essayer d'afficher l'adresse en question dans la boucle :
Code:
printf("%d => %x\n", foo, var_tab[foo]);

Axiome : le C ne fait aucune vérification sur ce que l'on fait dans la mémoire.
Théorème : quand on bricole des pointeurs de pointeurs sans trop documenter (commenter) ce que l'on fait et sans avoir les idées très claires sur les raisons d'utiliser des pointeurs, on écrit n'importe où en mémoire, et... crash (mais pas forcément tout de suite, sinon ce n'est pas drôle).

Il te faudrait surement un boucle qui fait les mallocs pour tous les objets t_var que tu veux stoquer.

C'est inutile en l'état. Il n'y a aucune raison d'utiliser un pointeur de pointeur en l'occurrence. Le but est juste d'allouer une zone mémoire de foo structures contigues. Il y a donc besoin d'un unique pointeur, sur le premier élément. Les autres seront accessibles via l'index.

Faisons simple : il y a un seul malloc, on a donc un seul pointeur.

(sbase qui n'a pourtant pas été appelée dans le while, ça n'a donc bien aucun sens on est d'accord ?).

Même chose. C'est pas parce que sbase n'est pas utilisé, ni dans le while, ni dans la fonction appelée, que son contenu n'a pas été écrasé par une malencontreuse manipulation à côté (un pointeur mal maîtrisé, un buffer qui déborde, etc...).

Bref, les pointeurs, ça se calcule sur le papier avant de se lancer dans le code, sinon on ne s'y retrouve plus, et on jardine joyeusement en mémoire. Et même quand on a tout bien calculé, avant de diffuser le code, l'utilisation d'un outil spécialisé dans la détection de ce genre de truc est fortement conseillé (insure, valgrind, pour ceux que je connais).
« Dernière édition: 26 Novembre 2008 à 21:45:56 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/54285

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #3 le: 26 Novembre 2008 à 21:54:46 »

Aucune des adresses n'est nulles en vérifiant, mais je les trouve bien éloigné les unes des autres...
3 => 0xbfbfebe4
2 => 0x4
1 => 0xbfbfebc4
0 => 0x804c000

En fait je me rend compte que c'est bien un pointeur sur un tableau de structure.
Il n'avait déjà pas lieu d'être : je n'utilise qu'un seul tableau de structure, pas un tableau de tableau. J'ai envoyé un pointeur sur ce tableau parce que sinon, je me retrouve avec un Bus Error.

Si je n'envoie pas un pointeur sur ce tableau mais bien le tableau (ce qui, en théorie ne devrait rien changer si ? C'est un tableau, c'est déjà un pointeur), je n'ai pas de segfault quelque soit la taille du tableau. Par contre, j'ai du Bus Error si j'essaye de lire le contenu de ce tableau à l'extérieur de la fonction ou je l'ai malloqué. A l'extérieur, la valeur des pointeurs est bien nulle. En tout cas tout ce qui n'est pas le premier élément.


Pour le deuxième, c'est donc peut-être la faute au jardinage (mais quand même... je n'ai fait aucun malloc moi-même, et le my_getnbr_base se contente de lire base, rien n'est écrit dessus, cette fonction ne fait que lire deux chaînes base et nbr, fait quelque calculs à partir d'eux et incrémente un int en fonction de ce qu'elle trouve).
Si c'était du jardinage, en admettant que je fasse dix copies de base, une des copies au moins devrait survivre à ce jardinage ?
Et pourquoi absolument tout les emplacement de ces deux chaînes sont remplacés par des '\0' ? C'est assez curieux quand même... c'est pas qu'on a récrit dessus, c'est qu'ils ont été libéré... c'est ça ?
« Dernière édition: 26 Novembre 2008 à 22:04:32 par The-Snake » Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54285

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


Voir le profil
« #4 le: 26 Novembre 2008 à 22:21:38 »

Désolé, mais je n'ai strictement rien compris à tout ce que tu as dit...

Je ne sais pas ce que tu veux dire par tableau de pointeur ou pointeur de tableau. Relis mon message. Tu as besoin d'un tableau de struct. Essaie d'imaginer comment tu ferais un tableau d'entier et fait la même chose. Oublie les **. Un malloc, un pointeur. Tout part de là. Tant que ce ne sera pas clair dans ta tête, pas la peine de coder et d'expérimenter des bus error, segmentation fault, et autres joyeusetés.

Si c'était du jardinage, en admettant que je fasse dix copies de base, une des copies au moins devrait survivre à ce jardinage ?

Ne sachant pas où et pourquoi ça jardine, comment peux-tu savoir comment ça jardine ?

Citation
Et pourquoi absolument tout les emplacement de ces deux chaînes sont remplacés par des '\0' ? C'est assez curieux quand même... c'est pas qu'on a récrit dessus, c'est qu'ils ont été libéré... c'est ça ?

Non, là, il va vraiment falloir réviser. Quand tu libères une zone allouée, l'adresse stockée dans ton pointeur n'est absolument pas modifiée (et donc pointe sur... n'importe quoi). Quand au contenu pointé... il n'est absolument pas touché non plus. Il est laissé tel quel. Si le contenu vaut zéro, c'est que quelqu'un a mis zéro dedans (et ce n'est ni la barrette de mémoire, ni le système d'exploitation, de leur propre initiative).

Note: toutes ses réflexions se basent sur les principes généraux du langage C. Certains compilos ou plugins tentent d'ajouter des contrôles pour éviter ses désagréments (et surtout lutter contre les erreurs de programmation qui introduiraient des failles de sécurité), mais ça ne sera jamais parfait : le C n'est absolument pas conçu pour ça, il vaut mieux se tourner vers des langages plus modernes.
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/54285

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #5 le: 26 Novembre 2008 à 23:57:56 »

char tab[10];

fonction(&tab);

J'ai envoyé un pointeur sur un tableau, un tableau étant une suite de pointeurs, c'est un pointeur sur un pointeur.
Effectivement, j'ai clarifié un peu la chose dans ma tête... j'ai fait quelque chose de tout aussi clair, mais de plus simple, et qui marche : le malloc est maintenant à l'intérieur du main, la boucle reste dans l'autre fonction. Esthétiquement le code est même plus beau comme ça... et sa compréhension en est plus rapide pour l'œil extérieur.
Et ça marche : je ne vois toujours pas bien la différence entre avant et maintenant mais bon... maintenant j'utilise beaucoup moins de pointeurs et ça limite pas mal les risque de se planter en effet.

Par contre pour l'autre code... je vois toujours pas.
Une chose est sur : ce n'est pas le getnbr_base qui en est la cause. Il marchait il n'y a pas plus de deux heures, quand j'ai commencé à écrire le programme : au début, le programme marchait impec', j'avais juste un décalage d'un emplacement en lisant mes tableaux, chose qui se passe bien après le getnbr_base.
J'ai modifié le détail, et d'un seul coup plus rien n'allait : en vérifiant les valeurs des variables, j'ai vu que c'était base qui n'existait plus. J'ai tout essayé en vain : désespéré, j'ai supprimé tout le reste du code et n'ai gardé que la partie que je vous ai montré (plus les déclarations de variables)... rien à faire, base n'existe toujours plus.

Je viens d'ailleurs de checker le code du getnbr_base. La seule ligne ou la variable base est utilisée, c'est celle-ci :
strlen(base);
Ca n'a vraiment pas de sens. Ce int getnbr_base(char *nbr, char *base) est tout ce qu'il y a de plus clair : je déclare une variable "result", je compte le nombre de caractères de "base", je compte le nombre de chiffres de "nbr", et je lis nbr caractère par caractère, et j'ajoute dans result la valeur de chaque caractère (déterminiée par sa position dans "base") multiplié par sa position dans la base et la longueur de la base... et je renvois result quand j'ai fini.
Dis comme ça, c'est peut-être pas très clair, mais je pense que vous avez compris le principe, c'est simple et c'est carrément impossible de se planter là dessus.
Il n'y a aucun moment ou se code pourrait lire à un endroit ou il ne devrait pas, et quand bien même, il n'écrirait pas dessus : il n'écrit que sur des variables qui ont été déclarées dans la fonction même, et aucune de ces variables n'est un pointeur. Que du local. Si encore il y avait une chance de se planter, ce serait au moins avec un tableau non ? Manque de bol il n'y en a pas. Ni tableau, ni pointeurs autre que ceux envoyés en arguments, qui eux ne subissent JAMAIS de modifications.

Je comprend pas.
Pour le premier morceau de code c'est vrai : j'avais quelques détails qui étaient pas justifiables et difficile à saisir.
Ce code là on le comprend très bien de la première à la dernière ligne. Il n'y a pas la moindre subtilité, on ne fait que lire des chaînes de caractères !

EDIT : ah... on peut oublier pour le moment. J'ai retrouvé un back up du code quand il marchait.
Le truc c'est que c'est encore mais alors ENCORE plus troublant. C'est EXACTEMENT le même code... mais avec quelques lignes après. Et ça marche. Et au moins on sait pourquoi ça marche.

Je vais perdre tout mes cheveux à ce rythme là.
« Dernière édition: 27 Novembre 2008 à 01:00:18 par The-Snake » Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54285

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


Voir le profil
« #6 le: 27 Novembre 2008 à 08:17:08 »

char tab[10];
fonction(&tab);

L'appel à la fonction est complètement tordu. Pourquoi passes-tu un pointeur de tableau en argument ?!? Et de toute façon, ça ne correspond pas non plus à ce que tu souhaites faire, puisque là ton tableau fait 10 entiers, alors que tu veux un tableau de foo entiers.

Citation
le malloc est maintenant à l'intérieur du main, la boucle reste dans l'autre fonction.

Bon, ben si ça marche mieux, je suis content pour toi, mais le problème n'est pas dans la position du malloc. Je me répète, mais ce n'est pas en bricolant un code qui plante que tu vas t'en sortir (au contraire, c'est le meilleur moyen pour pondre des horreurs). C'est la phase chiante de l'apprentissage du C : comprendre exactement comment ça marche, comment s'y prendre proprement et sans risque.

Citation
Par contre pour l'autre code... je vois toujours pas.

On n'a pas de boule de cristal ! Poste le code complet, sur pastebin par exemple. Et le code légèrement différent, qui fonctionne.

Citation
c'est carrément impossible de se planter là dessus.

Il est TRÈS facile de se planter, en C, et de passer des plombes à chercher une bêtise insignifiante en apparence qui a d'énormes conséquences.
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/54285

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #7 le: 27 Novembre 2008 à 09:39:37 »

Citation
Bon, ben si ça marche mieux, je suis content pour toi, mais le problème n'est pas dans la position du malloc. Je me répète, mais ce n'est pas en bricolant un code qui plante que tu vas t'en sortir (au contraire, c'est le meilleur moyen pour pondre des horreurs). C'est la phase chiante de l'apprentissage du C : comprendre exactement comment ça marche, comment s'y prendre proprement et sans risque.
Baah c'est pas vraiment de la bidouille. En fait c'est même parfaitement logique. J'ai besoin d'un tableau de structure d'une taille foo et j'ai besoin que tout les éléments type de cette structures soient initialisés à zéro. Je déclare un tableau var_tab, et je lui fais un malloc de sizeof(var_tab) * foo, puis je remplis les éléments type avec une boucle.
Je vois difficilement comment on pourrait faire plus simple que ça, et ça n'a absolument rien d'ambigu.

Ce qui est ambigu, c'est d'envoyer à une fonction un pointeur sur ce tableau pour le malloquer, là je suis d'accord, ça n'a pas de sens. Le tableau, c'est déjà un pointeur, donc on devrait juste l'envoyer lui.
Mais dans ce cas, pourquoi je ne peux pas lire ce tableau à l'extérieur de la fonction où je lui ai fait son malloc ?
Ca n'a rien de compliqué en soit.

Citation
int malloc_word(char *word, int len)
{
  word = malloc(sizeof(char) * (len + 1));
  if (!word)
    return (0);
  word[len] = '\0';
  len--;
  while (len >= 0)
    {
      word[len] = 'a';
     len--;
    }
  printf("%s\n", word); 
  return (1);
}

int main(int ac, char **av)
{
  char   *word;

  if (ac == 2)
  {
    if (!malloc_word(word, strlen(av[1])))
      return (-1);
    printf("%s\n", word);
    strcpy(word, av[1]);
    printf("%s\n", word);
  }
  return (0);
}

Ce code là n'a rien de compliqué ni d'ambigu n'est-ce pas ? C'est l'équivalent exact de ce que j'ai fait dans le mien, en utilisant juste un tableau de char au lieu d'un tableau de structure.
J'en déduis donc très logiquement que le troisième printf m'affichera le premier mot passé en paramètre. Et j'en déduis moins logiquement (c'est ce qui se passe dans mon code qui a la même forme) que le second printf, qui selon moi devrait afficher une suite de a de la longueur du mot passé en paramètre, n'affichera rien du tout (voir même pondra une segfault).
Maintenant, si à la place de ça malloc_word prend en paramètre char **word, int len, alors dans ce cas le second printf affichera effectivement une série de 'a'. Et là je me demande donc pourquoi je dois envoyer un pointeur ? Le tableau, c'est déjà un pointeur, le malloc_word travaille sur la même variable que celle du main !

J'ai testé ce code, il se passe effectivement ce que j'ai annoncé.
Le premier printf, dans la fonction du malloc, affiche une suite de a de la taille du mot en paramètre.
Le deuxième printf, dans le main, n'affiche rien.
Le troisième printf, dans le main encore, affiche le mot passé en paramètre.

Donc rien de plus simple : il y a simplement quelque chose qui m'échappe dans ce qui se passe ici.
C'est quand même quelque chose de très simple... donc je dois comprendre de travers.
Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54285

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


Voir le profil
« #8 le: 27 Novembre 2008 à 19:21:16 »

Bon, en reprenant le tout à tête reposée, je me rends compte que j'ai dit une bêtise en te demandant d'oublier les doubles pointeurs, mais faut avouer que je ne me serais pris de la sorte pour écrire ce genre de code. Ton but, ici, est d'allouer un pointeur, or, comme tu vas changer sa valeur (l'adresse sur laquelle il pointe) par malloc, il faut le passer par référence. Donc, oui, il te faut un **var dans ton prototype. En l'occurrence, voilà le bon code :

Code:
int malloc_word(char **word, int len)
{
*word = malloc(sizeof(char) * (len + 1));
if (!*word)
return (0);
(*word)[len] = '\0';
len--;
while (len >= 0) {
(*word)[len] = 'a';
len--;
}
return (1);
}

Mai pourquoi s'embêter avec une syntaxe aussi tordue ? Tu as une valeur à retourner, une seule : le pointeur en question (qui vaudra NULL si l'allocation s'est mal passée). Autant avoir le même prototype que malloc :

Code:
char *malloc_word2(int len) {
char *word = malloc(sizeof(char) * (len + 1));
if (NULL != word) {
word[len] = '\0';
len--;
while (len >= 0) {
word[len] = 'a';
len--;
}
}
return (word);
}

C'est quand même nettement moins casse-gueule, là, non ? Ah, et en passant, il va falloir commencer à appeler free de temps en temps, aussi.

«Keep it simple, stupid !» comme on dit.
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/54285

Membre Junior
**
Hors ligne Hors ligne
Messages: 78


Voir le profil
« #9 le: 27 Novembre 2008 à 22:20:39 »

J'appelle free, mais pas avant d'avoir utilisé la structure :p !
Ceci dit... heureusement que tu me le rappelles quand même, dans la nouvelle version de la fonction principale j'avais oublié de remettre le free -_-' ! Je me serais pris un sacré savon... d'ailleurs j'ai plutôt intérêt à mettre de l'ordre dans mes fichiers : je n'ai du KISS que le Stupid (des fonctions qui s'appellent dans tout les sens... va falloir que je prenne l'habitude de pas coder à la volée).

Pour le deuxième problème, j'ai récrit tout le programme et ça marche du tonnerre. Je sais pas vraiment ce que ça a de plus ou de moins, mais c'est le premier programme un poil conséquent que je code et qui marche parfaitement depuis un bon bout de temps... ça a quelque chose de fortement jouissif.

C'est quand même vachement plus tendu qu'il ne parait la gestion de la mémoire en C... quand on fait une connerie évidente, ça nous avertit pas, ça nous obéis au doigt et à l'œil... ça doit avoir un paquet d'avantages aussi ceci dit, mais j'attends de les voir.
Journalisée
_o_
Relecteur

Profil challenge

Classement : 42/54285

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


Voir le profil
« #10 le: 28 Novembre 2008 à 10:41:36 »

ça doit avoir un paquet d'avantages aussi ceci dit, mais j'attends de les voir.

C'est vite vu : taille, rapidité, et possibilité de faire des bricolages les plus immondes pour arriver à ses fins.

Je rappelle que sans le C, le monde de l'informatique ne serait pas ce qu'il est aujourd'hui. Le C a été conçu comme un langage simple, de bas niveau, ouvrant la voie à la portabilité du logiciel (rappelons qu'à l'époque, dans les années 70, chaque constructeur fournissait son propre système et ses propres logiciels avec sa plate-forme), donc à UNIX, et tout ce qui a pu en découler.

Voir l'article sur wikipedia à ce sujet.
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 à: