我在C程序中使用opendir/readdir
(cygwin上的GNU)将一些嵌套目录中的文件名收集到字符串数组中(该程序主要使用C89和早期的约定)。由于我事先不知道文件的数量,我决定使用malloc/realloc
来执行动态内存分配。指针数组通过递归调用来收集文件名。问题是在早期调用getlist()
期间存储的文件名在以后的存储步骤中被损坏。在进入子目录,执行对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("%sn",*traverse);
fprintf(pfile,"%sn",*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("%sn",*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 %sn", 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("%sn",*ptemp);
ptemp++;
}
printf("n-----------n");
}
}
}
(void) closedir (directory);
*traverse = NULL;
return filelist;
}
分配的内存不足。
由于没有为终止空字符分配足够的空间,然后执行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("%sn", *ptemp);
ptemp++;
}
ii
基于目录大小,但文件名的使用不包括"."one_answers".."。
可疑代码除了GTG之外还有其他问题。
这是AFAIK的一个有效解决方案。事实证明,我没有正确跟踪已经分配的指针数组元素的数量,因此在对realloc
的顺序调用过程中,我实际上用新数据覆盖了旧的内存位置。令人惊讶的是,这个错误的结果并没有更戏剧性——我想这是偶然的。
我的解决方案是传递第二个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("%sn",*traverse);
fprintf(pfile,"%sn",*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 %sn", filename);
filelist = getlist(filelist,pnumfiles,pnallocated,filename);
traverse = filelist + *pnumfiles;
}
}
}
*traverse = NULL;
(void) closedir (directory);
return filelist;
}