2014-10-18 48 views
0

我在C程序(GNU on cygwin)中使用opendir/readdir從一些嵌套目錄中將文件名收集到字符串數組中(該程序主要使用C89及更早版本的約定) 。由於我不知道文件的數量,我決定使用malloc/realloc來執行動態內存分配。一個指針數組通過遞歸調用來收集文件名。問題是在以後的存儲步驟中早期調用getlist()時存儲的文件名被損壞。在進入子目錄後,執行第二次調用realloc並從子目錄出現,執行realloc之後的存儲位於邊緣的字符串逐漸損壞,因爲在父目錄中收集了額外的文件名。realloc在遞歸存儲目錄/文件名期間咀嚼字符串數組

如果我使用單個malloc賦值來分配內存來創建一個較大的初始指針數組,我避免了這個問題,但我希望能夠使用realloc。任何人都可以指出我做錯了什麼,特別是我猜這是我在這種情況下如何使用realloc

#include <string.h> 
#include <stdlib.h> 
#include <signal.h> 

#include <stdio.h> 
#include <stddef.h> 
#include <sys/types.h> 
#include <dirent.h> 
#include <sys/stat.h> 

// function prototypes 
char ** getlist(char ** filelist,long int *numfiles,char *dirname); 

extern int stat (const char *filename, struct stat *buf); 
extern DIR * opendir (const char *_dirname); 
extern struct dirent * readdir (DIR *dirstream); 

int main(int narg, char* argv[]) 
{ 

    // vars 
    char * dirname; 
    char **filelist, **traverse; 
    long int numfiles =0; 
    FILE* pfile; 

    //.................................... 
    // and go ... 

    if ((filelist = (char **)malloc(sizeof(char *))) == NULL) 
    { 
      printf("Fatal malloc error!\n"); 
      exit(3); 
    } 
    filelist = getlist(filelist,&numfiles,argv[1]); // argv[1] is dir name 

    // list the stored filenames and write to file 

    pfile = fopen("listoutput.txt","w"); 
    printf("Stored filenames (N=%i):\n",numfiles); 
    traverse = filelist;  
    while(*traverse) 
    { 
     printf("%s\n",*traverse); 
     fprintf(pfile,"%s\n",*traverse); 
     traverse++; 
    } 
    fclose(pfile); 

    // free etc should go here... 

    return 0 ; 
} 

//------------------------------------------------------------------------------- 

char ** getlist(char** filelist, long int *numfiles, char* dirname) 
{ 

    // variables 
    char filename[200]; 
    char dirname_[200]; 
    DIR * directory; 
    struct dirent * file; 

    strcpy(dirname_,dirname); 

    // for checking file type 
    // macro: int S_ISREG (mode_t m) 
    struct stat* filestat = malloc(sizeof(struct stat)); 
    int sizeofchar = sizeof(char); // fields: mode_t st_mode, unsigned char d_namlen 

    char **traverse, **ptemp; 

    //aux 
    long int ii, icheck;  

    // check number of valid files in dirname and allocate memory in char pointer array 
    ii=0; 
    directory = opendir (dirname_); 
    while(file = readdir(directory)) 
    { 
     sprintf(filename,"%s/%s",_dirname_,file->d_name); 
     icheck = stat(filename,filestat); 
     if (icheck==0) 
     { 
      if (S_ISREG(filestat->st_mode)) ii++; 
     } 
     else 
     { 
      printf("Couldn't check file type of file \"%s\" (icheck = %i)\n", filename, icheck); 
     } 
    } 

    // generate enough room for all the filename strings 

    if ((filelist=(char **)realloc(filelist,sizeof(char *)*(*numfiles+ii+1))) == NULL) 
    { 
     printf("Fatal realloc error!\n"); 
     exit(3); 
    } 

    traverse = filelist + *numfiles; 

    // now store the filenames in filelist ... 

    (void) rewinddir (directory); 
    while(file = readdir(directory)) 
    { 
     sprintf(filename,"%s/%s",dirname_,file->d_name); 
     icheck = stat(filename,filestat); 
     if (icheck==0 && S_ISREG(filestat->st_mode)) 
     { 
      *traverse = (char *)malloc(sizeofchar*(strlen(filename)+1)); 
      strcpy(*traverse,filename); 
      traverse++; 
      (*numfiles)++; 

      // spit out what we have so far 
      printf("\nCurrent list (looping):\n-----------\n"); 
      ptemp = filelist; 
      ii=*numfiles; 
      while(ii--) 
      { 
       printf("%s\n",*ptemp); 
       ptemp++; 
      } 
      printf("\n-----------\n");   

     //sleep(1); 
     } 
     else if (icheck==0 && S_ISDIR(filestat->st_mode)) 
     { 
      if (strcmp(file->d_name,".")!=0 && strcmp(file->d_name,"..")!=0) 
      { 
       printf("Processing folder %s\n", filename); 
       filelist = getlist(filelist,numfiles,filename); 
       traverse = filelist + *numfiles; 

       // spit out what we have so far 
       printf("\nCurrent list (returned from getlist):\n-----------\n"); 
       ptemp = filelist; 
       while(*ptemp) 
       { 
        printf("%s\n",*ptemp); 
        ptemp++; 
       } 
       printf("\n-----------\n"); 

      }  
     } 
    } 
    (void) closedir (directory); 

    *traverse = NULL; 

    return filelist;  
} 
+0

想法:代碼省略了各種錯誤檢查 - 也許是在趕上這段代碼。檢查'malloc/realloc'返回。確保'sprintf(文件名,「%s /%s」,_ dirname,file-> d_name);'具有足夠的大小等等。另外,在動態系統中,目錄的重新掃描可能會比第一次更有效。建議檢查文件名列表的大小是否足夠。也可能要填零分配的內存。 – chux 2014-10-18 22:35:23

+2

順便說一下,模式'ptr = realloc(ptr,...)'是一個非常糟糕的做法。如果'realloc'失敗,您將無法訪問原始指針並泄漏相關內存。 Google對於「realloc內存泄漏」的原因有很多。 – 2014-10-18 23:20:19

+0

@chux,到目前爲止沒有任何改進,將繼續小打小鬧。可能會嘗試執行兩次dir搜索,第一次在單個malloc之前收集文件名的總數。 – 2014-10-18 23:29:48

回答

0

這是AFAIK一個工作解決方案。事實證明,我沒有正確跟蹤已分配的指針數組元素的數量,因此在連續調用realloc期間,我有效地用新數據覆蓋舊內存位置。令人驚訝的是,這個bug的結果並不是更戲劇化 - 我猜想是偶然的。

我的解決方案是傳遞第二個int跟蹤要存儲的字符串(文件名)的數量。我可以在遞歸的每個級別交替地掃描指針數組,以便即時確定這個數字。

最後我不知道是否將這作爲一個鏈表實現不會更好。下次再說吧。

#include <string.h> 
#include <stdlib.h> 
#include <signal.h> 

#include <stdio.h> 
#include <stddef.h> 
#include <sys/types.h> 
#include <dirent.h> 
#include <sys/stat.h> 

typedef struct stat STAT; 


// prototypes 
extern int stat (const char *filename, STAT *buf); 
extern DIR * opendir (const char *dirname); 
extern struct dirent * readdir (DIR *dirstream); 


char ** getlist(char ** filelist,long int *pnumfiles, long int *pnallocated,char *dirname); 


int main(int narg, char* argv[]) 
{ 
    // vars 

    FILE* pfile; 
    char **filelist, **traverse; 
    long int numfiles, nallocated; 

    //.................................... 
    // and go ... 

    if ((filelist = (char **)malloc(sizeof(char *))) == NULL) 
     { printf("Fatal malloc error!\n"); exit(1);} 
    nallocated = 1; 
    numfiles = 0; 

    filelist = getlist(filelist,&numfiles,&nallocated,argv[1]);   // argv[1] should be a directory name 

    // list the stored filenames and store to file 

    pfile = fopen("listoutput.txt","w"); 
    if (pfile == NULL) 
     { fprintf(stderr, "Could not open file for output!"); exit(4);}; 

    printf("Stored filenames (N=%i):\n",numfiles); 
    traverse = filelist;  
    while(*traverse) 
    { 
     printf("%s\n",*traverse); 
     fprintf(pfile,"%s\n",*traverse); 
     traverse++; 
    } 
    printf("\nDONE!\n"); 
    fclose(pfile); 

    return 0; 
} 

//------------------------------------------------------------------------------- 

char ** getlist(char** filelist, long int *pnumfiles, long int *pnallocated, char* dirname) 
{ 
    // variables 
    char filename[200]; 
    char dirname_[200]; 
    DIR * directory; 
    struct dirent * file; 

    // for checking file type 
    // macro: int S_ISREG (mode_t m) 

    STAT* filestat; // fields: mode_t st_mode, unsigned char d_namlen 

    long int ifile = 0; // number of files to store from current dir 
    int sizeofchar = sizeof(char); // = 1 

    //aux 
    long int icheck;  
    char **traverse, **ptemp; 

    // and go .... 

    // copy string to const char array because opendir demands one 

    strcpy(dirname_,dirname); 

    if ((filestat = malloc(sizeof(STAT))) == NULL) 
     { 
      printf("Error allocating memory for filestat!"); 
      exit(2); 
     } 

    // check number of valid files in dirname and allocate memory in char pointer array 

    directory = opendir (dirname_); 
    while(file = readdir(directory)) 
    { 
     sprintf(filename,"%s/%s",dirname_,file->d_name); 
     icheck = stat(filename,filestat); 
     if (icheck==0) 
     { 
      if (S_ISREG(filestat->st_mode)) ifile++; 
     } 
     else 
     { 
      printf("Couldn't check file type of file \"%s\" (icheck = %i)\n", filename, icheck); 
     } 
    } 

    // generate enough room for all the filename strings 

    *pnallocated = *pnallocated + ifile; 
    if ((filelist=(char **)realloc(filelist,sizeof(char *)*(*pnallocated))) == NULL) 
     { 
      printf("Fatal realloc error!\n"); 
      exit(2); 
     } 
    traverse = filelist + *pnumfiles; 

    // now store the filenames in filelist ... 

    (void) rewinddir (directory); 
    while(file = readdir(directory)) 
    { 
     sprintf(filename,"%s/%s",dirname_,file->d_name); 
     icheck = stat(filename,filestat); 
     if (icheck==0 && S_ISREG(filestat->st_mode)) 
     { 
      size_t size = strlen(filename) + 1; // Add 1! 
      if ((*traverse = malloc(size)) == NULL) 
      { 
       printf("Fatal malloc error!\n"); 
       exit(3); 
      } 
      memcpy(*traverse, filename, size); 
      traverse++; 
      (*pnumfiles)++; 
     } 
     else if (icheck==0 && S_ISDIR(filestat->st_mode)) 
     { 
      if (strcmp(file->d_name,".")!=0 && strcmp(file->d_name,"..")!=0) 
      { 
       printf("Processing folder %s\n", filename); 
       filelist = getlist(filelist,pnumfiles,pnallocated,filename); 
       traverse = filelist + *pnumfiles; 
      }  
     } 
    } 
    *traverse = NULL; 
    (void) closedir (directory);  

    return filelist;  
} 
+0

我不認爲你應該接受這個答案(來自你自己)!你在這裏做了一些很好的工作,並且隨着你的進行而學習:但是你真的認爲塊中的OS內存句柄是問題嗎?你在這裏所做的只是分配比你想象的更多的內存......可能隱藏了問題(直到它意外地讓你意外地......)而不是解決問題。繼續嘗試! – AAT 2014-10-20 12:46:19

+0

@AAT感謝您的評論 - 我發現了錯誤並重寫了我的答案。問題解決了! – 2014-10-20 21:11:51

+0

幹得好,你在那裏做了很多調試! – AAT 2014-10-21 07:46:15

1

分配的內存不足。

通過未分配足夠空間來終止空字符,然後執行strcpy(),內存可能會損壞。

// *traverse = (char *)malloc(sizeofchar*strlen(filename)); 
// strcpy(*traverse,filename); 

size_t size = strlen(filename) + 1; // Add 1! 
*traverse = malloc(size); 
memecpy(*traverse, filename, size); 

輕微:無需從malloc()投下退貨。一旦代碼知道大小,用途,memcpy()strcpy()

-

更新

,而非打印ii次,打印,直到*temp == NULL

while (ii--) { 
    if (*ptemp == NULL) break; 
    printf("%s\n", *ptemp); 
    ptemp++; 
    } 

ii是目錄大小基地,但文件名的使用不包括「」和「..」。

可疑代碼仍然有其他問題,但GTG。

+0

謝謝 - 你對'strlen(文件名)+ 1'是正確的。也感謝您的建議。不幸的是,這個更正沒有解決問題(!?) – 2014-10-18 22:09:17

+0

再次感謝 - 我認爲指針和本地mem管理是好的(我已經嘗試過strncpy,memcpy,memset - 沒有改進)。正如我在上面寫的,我想不管怎麼說,迭代調用會弄亂數據,由於指針沙拉,存儲在較高階段的數據以某種方式破壞存儲在較低階段的數據。也許有些變量應該聲明爲靜態? – 2014-10-19 11:21:50

+0

強大的解決方案不需要靜態變量。 – chux 2014-10-19 13:44:56