IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C Discussion :

[readdir() Linux] effet de bord


Sujet :

C

  1. #1
    Membre averti

    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2012
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2012
    Messages : 313
    Points : 354
    Points
    354
    Par défaut [readdir() Linux] effet de bord
    Bonjour, bonne année et meilleurs voeux ^^

    Récemment je me suis replongé dans la programmation en C et j'ai été confronté a un "effet de bord" assez déroutant...
    J'écris une fonction qui va lire l'arborescence d'un répertoire, chaque "entrée" (un nom de fichier régulier sous Linux) est "insérée" dans une liste doublement chaînée...
    La librairie qui gère les listes doublement chaînées a été éprouvée et dans d'autres applications où j'insère des chaînes de caractères je n'ai jamais rencontré ce que j'ai observé en testant cette fonction,
    je pars donc du principe que "cela ne vient pas de ma librairie".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    void BrowseFiles(char *pPath)
    {
    	DIR           *desc_REP;	
    	struct dirent *fichier;
     
    	char Buffer[FILENAME_MAX]="\0";
     
    	if(pPath==NULL) return;
    	desc_REP = opendir(pPath);
    	if(desc_REP==NULL) return;
    Voici la boucle principale dans laquelle nous retrouvons le point de récursion...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    	do
    	{
    		fichier=readdir(desc_REP);
    		if(fichier==NULL) break;
    		// ignorer les répertoires spéciaux '.' et '..' de la liste
    		if(strcmp(fichier->d_name,".") && strcmp(fichier->d_name,".."))
    		{
    			// si le nom du fichier est différent de '.' et '..'
    
    			if(fichier->d_type==DT_DIR)
    			{
    				// il s'agit d'un répertoire
    				memcpy(Buffer,0,FILENAME_MAX);
    Ici j'ai changé en Buffer[0]='\0'... apparemment si je ne passe pas de paramètre en ligne de commande ça provoque une erreur de segmentation :{
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    				getcwd(Buffer,FILENAME_MAX);
    				strncat(Buffer,"/",1);
    				strncat(Buffer,fichier->d_name,strlen(fichier->d_name));
     
    				chdir(Buffer);
    				BrowseFiles(Buffer); // point de récursion
    				chdir("..");
    			}
    			if(fichier->d_type==DT_REG) 
    			{
    				if(strstr(fichier->d_name," -- "))				
    				{
    C'est ici que ça coince ^^
    En regardant la valeur retournée à "fichier" je me suis dit que la fonction readdir() faisait un malloc() ou calloc() (j'ai pas encore pris la peine d'aller voir dans les sources)
    et que le pointeur était retourné en fin de traitement, donc je me suis dit que je pouvais allégremment prendre le contenu du champs d_name de cette structure et l'insérer
    dans ma liste chaînée...
    Le soucis c'est qu'en regardant le résultat lors de l'affichage du contenu de la liste, c'est du "n'importe quoi" comme si à un moment donné il n'y avait plus de '\0' en fin
    de chaîne de caractères, un effet de bord classique, qui heureusement ici ne se solde pas par un core dump, une erreur de segmentation.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    #ifndef SHOWBUG 
    					char *toadd=calloc(1,strlen(fichier->d_name));
    					strcpy(toadd,fichier->d_name);
    					lc_insert((void*)toadd,ll_NomsFichiers,cssmuserdef,sizeof(char*));
    Les instructions ci-dessus permettent d'afficher un contenu cohérent des données...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    #else
    					lc_insert((void*)fichier->d_name,ll_NomsFichiers,cssmuserdef,sizeof(char*));	
    #endif
    L'instruction ci-dessus provoque un affichage des données quelque peu "foireux"... symptômatique des effets de bords...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    				}
    			}
    		}
     
    	}while(fichier);
    }
    C'est tout de même étrange de devoir passer par la copie (j'ai volontairement évité de faire toadd=fichier->d_name et préféré utiliser strcpy() ) alors que readdir() est censé fournir un champs d_name valide.
    Voici ce que j'ai observé au niveau du "header" /usr/lib/bits/dirent.h...
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    struct dirent64
      {
        __ino64_t d_ino;
        __off64_t d_off;
        unsigned short int d_reclen;
        unsigned char d_type;
        char d_name[256];		/* We must not include limits.h! */
      };
    Les noms de mes fichiers ne font jamais plus de 256 caractères... je suis un peu dérouté par rapport au "comportement" de mon application...
    Je pense que d_name n'est jamais liée à un malloc() ou calloc()... je continues de chercher en espérant que l'accès au système de fichier et le changement du contenu de d_name ne fasse pas partie d'une fonction écrite en assembleur parce que là je vais galèrer ^^

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 631
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 631
    Points : 10 558
    Points
    10 558
    Par défaut
    Erreur de noob : documentation de strlen (<- lien cplusplus.com en anglais)
    C'est la taille sans le caractère sentinelle '\0'. Il faut allouer avec "+ 1" et ajouter ce caractère (strcpy ne le copie pas mais lis la documentation)

    Et ensuite ton calloc est mauvais : tu fais 1 écriture complète pour écrire des '\0' ... qui vont être écrasés par le strcpy

  3. #3
    Membre averti

    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2012
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2012
    Messages : 313
    Points : 354
    Points
    354
    Par défaut
    Oui il faut dire que j'ai essayé un peu à l'aveuglette... j'aurais plutôt dû faire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    calloc(strlen(fichier->d_name)+1,sizeof(char));
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    malloc(strlen(fichier->d_name)+1);
    J'aurais dû me concentrer d'avantage lol ^^

    Mais je ne vois toujours pas où ça coince mis à part que je partais du principe que d_name était un char* alors que c'est un char[256], AFAIK.
    Dans cette partie de code ça marche, j'ai pas d'effet de bord et les résultats obtenus sont ceux escomptés...

    Par contre si je fais toadd=fichier->d_name; au lieu du strcpy() <-- ça part en sucette comme dans la partie #else ... #endif.
    Exactement les mêmes symptômes.

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 631
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 631
    Points : 10 558
    Points
    10 558
    Par défaut
    Citation Envoyé par hurukan Voir le message
    Par contre si je fais toadd=fichier->d_name; au lieu du strcpy() <-- ça part en sucette comme dans la partie #else ... #endif.
    Exactement les mêmes symptômes.
    dans le documentation de readir ce n'est pas clair, mais c'est écrit que la structure peut être statique. En gros, on utilise la même variable pour récupérer 1 entrée.

    Donc, il faut sûrement recopier ta structure (et tous les champs) (deep copy et non shallow copy) avant d'utiliser l'entrée.

  5. #5
    Membre averti

    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2012
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2012
    Messages : 313
    Points : 354
    Points
    354
    Par défaut
    Citation Envoyé par foetus Voir le message
    dans le documentation de readir ce n'est pas clair, mais c'est écrit que que la structure peut être statique. En gros, on utilise la même variable pour récupérer 1 entrée.

    Donc, il faut sûrement recopier ta structure (et tous les champs) (deep copy et non shallow copy) avant d'utiliser l'entrée.
    Oui, j'avais lu le man de readdir() entre deux rhums... ça n'a pas été mieux ^^
    Oui, je pense que c'est ça: il faut copier le contenu de d_name sinon ça va pas aller.

    Valgrind est gentil avec moi:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    sirius:/netbeans/2022/magic2022 # valgrind -s --leak-check=full ./magic2022 /datas2/2021/mtga/2021/diamond\ déc/
    ==31102== Memcheck, a memory error detector
    ==31102== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==31102== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
    ==31102== Command: ./magic2022 /datas2/2021/mtga/2021/diamond\ d__c/
    ==31102== 
    ==31102== 
    ==31102== HEAP SUMMARY:
    ==31102==     in use at exit: 36,543 bytes in 90 blocks
    ==31102==   total heap usage: 375 allocs, 285 frees, 60,101 bytes allocated
    ==31102== 
    ==31102== 3,666 bytes in 87 blocks are definitely lost in loss record 3 of 4
    ==31102==    at 0x4C2E2DF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==31102==    by 0x400DD0: BrowseFiles (magicstats.c:146)
    ==31102==    by 0x400BE9: main (magicstats.c:80)
    ==31102== 
    ==31102== 32,816 bytes in 1 blocks are definitely lost in loss record 4 of 4
    ==31102==    at 0x4C2E2DF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
    ==31102==    by 0x586067B: __alloc_dir (in /lib64/libc-2.26.so)
    ==31102==    by 0x586078C: opendir_tail (in /lib64/libc-2.26.so)
    ==31102==    by 0x400C76: BrowseFiles (magicstats.c:110)
    ==31102==    by 0x400BE9: main (magicstats.c:80)
    ==31102== 
    ==31102== LEAK SUMMARY:
    ==31102==    definitely lost: 36,482 bytes in 88 blocks
    ==31102==    indirectly lost: 0 bytes in 0 blocks
    ==31102==      possibly lost: 0 bytes in 0 blocks
    ==31102==    still reachable: 61 bytes in 2 blocks
    ==31102==         suppressed: 0 bytes in 0 blocks
    ==31102== Reachable blocks (those to which a pointer was found) are not shown.
    ==31102== To see them, rerun with: --leak-check=full --show-leak-kinds=all
    ==31102== 
    ==31102== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

  6. #6
    Membre expérimenté Avatar de edgarjacobs
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2011
    Messages
    625
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 64
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2011
    Messages : 625
    Points : 1 564
    Points
    1 564
    Par défaut
    Citation Envoyé par foetus Voir le message
    (....) Il faut allouer avec "+ 1" et ajouter ce caractère (strcpy ne le copie pas mais lis la documentation) (....)
    Si si, strcpy(() copie bien le \0 final.
    On écrit "J'ai tort" ; "tord" est la conjugaison du verbre "tordre" à la 3ème personne de l'indicatif présent

  7. #7
    Membre averti

    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2012
    Messages
    313
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2012
    Messages : 313
    Points : 354
    Points
    354
    Par défaut
    Citation Envoyé par edgarjacobs Voir le message
    Si si, strcpy(() copie bien le \0 final.
    oui ça je ne me suis pas trop fait de soucis ^^

    The stpcpy() and strcpy() functions shall copy the string pointed to by s2 (including the termi-
    nating NUL character) into the array pointed to by s1.

  8. #8
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 690
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 690
    Points : 30 986
    Points
    30 986
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par foetus Voir le message
    strcpy ne le copie pas
    Sisi, strcpy() rajoute le'\0' en fin de copie. Ce sont les fonctions strn...() (strncpy, strncat) qui ne mettent pas le '\0' quand "n" a été atteint.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Effet de bord...
    Par pierre50 dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 15
    Dernier message: 12/10/2005, 18h11
  2. Effet de bord
    Par Clad3 dans le forum OpenGL
    Réponses: 11
    Dernier message: 04/10/2005, 14h38

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo