Reputation: 75
I'm trying to store a list of files with a .txt extension into an array from my current working directory. I've gotten as far as getting the array together, now I just need to return it. I'm new to C and I'm not used to the pointer situation. How can I return an array of char pointers? I have the array created, and its allocated based on how many Text files I find in the directory.
I'm getting two errors when I try to compile which leads me to believe that my understanding of pointers is not what I thought it was. I also have been reading that my array will be destroyed when returning from the function, because it is on the stack. I'm not sure how to fix that. Any help or criticism would be welcome.
// prototypes
char* getLine();
void replaceWord();
char * getTxtFilesInDir(char*);
bool isTxt(char *);
int main(int argc, char **argv) {
// Check to see if correct number of arguments is given
if (argc < 4) {
printf("ERROR: Not enough parameters supplied. Please supply a find,"
"replace, and prefix parameter and try again.\n");
return -1;
}
// Initialize variables
char *find, *replace, *prefix;
find=argv[1];
replace=argv[2];
prefix=argv[3];
char cd[1024];
// Get the Current working directory to grab files
if (getcwd(cd, sizeof(cd)) != NULL) {
printf("Current working dir is: %s\n", cd);
} else {
perror("getcwd() error");
}
// Get the values of the arguments
printf("Value of find: %s\nValue of replace: %s\nValue of prefix: %s\n",
find, replace, prefix);
// create an array of the files that are valid
char* files = getTxtFilesInDir(cd);
return 0;
}
char* getTxtFilesInDir(char* directory) {
DIR *d;
struct dirent *dir;
d = opendir(directory);
int n=0, i=0;
// get the number of text files in the directory
if (d) {
while((dir = readdir(d)) != NULL) {
if (isTxt(dir->d_name)) {
n++;
}
}
}
rewinddir(d);
// Create the array of text files depending on the size
static char* txtFiles[n];
// add the text files to the array
if (d) {
printf("Found: \n");
while ((dir = readdir(d)) != NULL)
{
if (isTxt(dir->d_name)) {
printf("%s\n", dir->d_name);
txtFiles[i]= (char*) malloc (strlen(dir->d_name)+1);
strncpy(txtFiles[i], dir->d_name, strlen(dir->d_name));
i++;
}
}
closedir(d);
}
return txtFiles;
}
bool isTxt(char *file) {
size_t len = strlen(file);
return len > 4 && strcmp(file + len -4, ".txt") == 0;
}
Upvotes: 1
Views: 70
Reputation: 13590
If you want that getTxtFilesInDir
returns an array of strings, it should
return a char**
, not a char*
.
Also you don't need to declare the variable as static
. You declare a variable
in a function as static
, when you want that the variable remains the same for
all calls of the function. In this case this is probably not what you want to
do.
Without modifying too much of your initial code, you can first allocate memory
for an arrray of strings, and then resize it when you've got a new entry. In
this example I do that at once, because realloc(NULL, somesize)
is the same as
doing malloc(somesize)
. That's why it's important to initialize *tmp = 0
and
txtFiles = NULL
, so this trick works. You should also pass a pointer to an size_t
where you store the number of entries:
char **getTxtFilesInDir(const char* directory, size_t *len) {
if(directory == NULL || len == NULL)
return NULL;
...
*len = 0;
char **txtFiles = NULL, **tmp;
char *str;
if (d) {
printf("Found: \n");
while ((dir = readdir(d)) != NULL)
{
if (isTxt(dir->d_name))
{
tmp = realloc(txtFiles, (len+1) * sizeof *textFiles);
if(tmp == NULL)
return txtFiles; // return what you've got so far
str = malloc(strlen(dir->d_name) + 1);
if(str == NULL)
{
if(txtFiles == NULL) // first time, free everything
{
free(tmp);
return NULL;
}
return tmp; // return all you've got so far
}
strcpy(str, dir->d_name); // no need of strcnpy, you've allocated
// enough memory
txtFiles = tmp;
txtFiles[(*len)++] = tmp;
}
}
closedir(d);
return txtFiles;
}
The important bits here is 1. how you expand the memory with realloc
. Then it
allocates memory for the string using malloc
. I do that before txtFiles = tmp;
,
so that I don't have to write to many if(...==NULL)
. If something goes
wrong along the way, the function returns all the file names it already has
stored.
Now in main
you do:
int main(int argc, char **argv)
{
...
size_t len = 0;
char **files = getTxtFilesInDir(cd, &len);
if(file == NULL)
{
fprintf(stderr, "No files found\n");
return 1;
}
for(size_t i = 0; i < len; ++i)
printf("file: %s\n", files[i]);
// freeing memory
for(size_t i = 0; i < len; ++i)
free(files[i]);
free(files);
return 0;
}
I also want to comment on your orginal way of copying:
strncpy(txtFiles[i], dir->d_name, strlen(dir->d_name));
strncpy
is great when you want to limit the number of bytes to be copied. In
your case you've already allocated enough memory, so there is no need to. And
when you use strncpy
, the limit should be bound to the number of bytes
available to the destination, not the source, otherwise you might copy more
bytes than it should. In your case you won't get a valid c-string, because you
are limiting to copy to the lenth of dir->d_name
, so the '\0'
-terminating
won't be copied.
man strcpy
#include <string.h> char *strcpy(char *dest, const char *src); char *strncpy(char *dest, const char *src, size_t n);
[...]
The
strncpy()
function is similar, except that at mostn
bytes ofsrc
are copied. Warning: If there is no null byte among the firstn
bytes ofsrc
, the string placed in dest will not be null-terminated.
When you use strncpy
you must make sure that the copy is a valid string by
setting the '\0'
-terminating byte yourself. Because you've allocated
strlen(dir->d_name)+1
bytes for the string:
strncpy(txtFiles[i], dir->d_name, strlen(dir->d_name));
textFiles[i][strlen(dir->d_name)] = 0;
Also the exit value of a program is a unsigned
value, it goes from 0 to 255.
In main
you should return 1
instead of -1
on error.
Upvotes: 1