2

I am having trouble sorting a dirent struct in C. I have tried everything and cannot get the values of my struct array to appear in my comparison. My code looks like this:

void printSortedNames(){

    struct dirent **file_list = (dirent**)malloc(5 * sizeof(dirent*));

    int i = 0;
    for (i = 0; i < directory_size; ++i){
        file_list[i] = (dirent*)malloc(50 * sizeof(dirent));
    }

    DIR *dir;
    struct dirent *sd;

    dir = opendir(".");

    if (dir == NULL){
        printf("Error! unable to open directory.\n");
        exit(1);
    }

    int count = 0;
    while ((sd = readdir(dir)) != NULL){
        file_list[count] = sd;
        printf("%s\n", file_list[count]->d_name);
        ++count;
    }

    size_t file_list_size = sizeof(&file_list) / sizeof(struct dirent);

    qsort(file_list, file_list_size, sizeof(struct dirent), sizeCompare);
}

I have created a simple function sizeCompare to show that my function is working but I get null values. My function looks like:

int sizeCompare(const void* a, const void* b){

    printf("%s\n", ((const struct dirent*)a)->d_name);
}

Can someone explain to me why my sizeCompare is not retrieve the array values correctly?

UPDATE: I have tried playing around with the size in the qsort and my value as a result was no longer null. the following line gives me an output:

qsort(file_list, 1000, sizeof(struct dirent), sizeCompare);

Obviously 1000 is not a good solution. Does anybody know the correct size for an array like this?

UPDATE 2: sizeCompare function only takes the first parameter and the second one is null.

int sizeCompare(const void* a, const void* b){

    const struct dirent *first_dirent = *(const struct dirent **) a;
    const struct dirent *second_dirent = *(const struct dirent **) b;
    .......
    //first one works but second one is NULL
}
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
VMA92
  • 469
  • 2
  • 8
  • 19
  • this line: `file_list[count] = sd;` only copies a pointer. What you actually needs is similar to: `memcpy( file_list[0], sd, sizeof( struct dirent) );` – user3629249 Sep 27 '15 at 19:00
  • You don't need to cast the `void *`. – Iharob Al Asimi Sep 27 '15 at 19:01
  • @user3629249 Indeed, this code leaks memory. Not only that, `closedir()` will deallocate all the pointers leaving dangling pointers in the array and leading subsequently to *undefined behavior* when the OP dereferences them. Although there is no `closedir()` in the code which means there is another memory leak. – Iharob Al Asimi Sep 27 '15 at 19:01
  • @user3629249 thanks for pointing this out. However, I am still having issues with the qsort method. Running the code in the debugger. it seems as though the sizeCompare function does not get called – VMA92 Sep 27 '15 at 19:06
  • @iharob which line are you refferring to when you say I don't need to cast the void? – VMA92 Sep 27 '15 at 19:11
  • `(struct dirent **) malloc( ... )` you don't need the cast because `void *` is automatically coverted to any pointer type. – Iharob Al Asimi Sep 27 '15 at 19:23

2 Answers2

4
  1. In the comparison function you need to dereference the pointers by first casting to struct dirent **, like this

    const struct dirent *first_dirent = *(const struct dirent **) first_parameter;
    

    this is because the address of each element is passed and since elements are pointers, the pointers passed to the funcion are pointers to pointers. Their void * addresses are the same, but you can't cast const struct dirent ** directly to const struct dirent *.

  2. You have another important problem this,

    file_list_size = sizeof(&file_list) / sizeof(struct dirent);
    

    is wrong, try to print the value and see and it should be1

    file_list_size = count;
    

    because your code computes the size of a pointer divided by the size of struct dirent which is probably resulting in 0, read about the sizeof operator, it's result depends on the passed argument. When it's a variable, the size of the type is the result, when the variable is an array it's the size of the array.

    Since file_list is a pointer to pointer, i.e. Not an array, then the result of

    file_list_size = sizeof(&file_list) / sizeof(struct dirent);
    

    is not what you think it is or what it should actually be.

  3. There is no correct size, perhaps you should count the entries first and predict a value for the first malloc() in your code. Or use realloc() and dynamically count the entries and allocate the poitners simultaneously.

Also:

  • Your code leaks memory as pointed out by @user3629249 in this comment

  • You don't need to cast the return value from malloc()

  • Try not to mix code with declarations, it makes it hard to track variables and their scope.

  • You allocate space for 5 struct dirent * pointers but you never check for the count variable whether it reached or went beyond that value. That could lead to undefined behavior.

  • Your code also leaks memory because you never call closedir().

Here is an example of alphabetically sorting the entries

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

#include <dirent.h>

int
compareSize(const void *const A, const void *const B)
{
    return strcmp((*(struct dirent **) A)->d_name, (*(struct dirent **) B)->d_name);
}

void printSortedNames(const char *const path)
{
    int count;
    DIR *dir;
    struct dirent **list;
    struct dirent *entry;

    dir = opendir(path);
    if (dir == NULL)
    {
        fprintf(stderr, "cannot open `%s'\n", path);
        return;
    }

    /* First determine the number of entries */
    count = 0;
    while ((entry = readdir(dir)) != NULL)
        ++count;
    /* Allocate enough space */
    list = malloc(count * sizeof(*list));
    if (list == NULL)
    {
        closedir(dir);
        fprintf(stderr, "memory exhausted.\n");
        return;
    }
    /* You don't need to allocate the list elements
     * you can just store pointers to them in the
     * pointer array `list'
     */
    rewinddir(dir); /* reset position */
    /* Save the pointers allocated by `opendir()' */
    count = 0;
    while ((entry = readdir(dir)) != NULL)
        list[count++] = entry;
    /* Call `qsort()', read about the `sizeof' operator */
    qsort(list, count, sizeof(*list), compareSize);
    /* Print the sorted entries now */
    for (int index = 0 ; index < count ; ++index)
        fprintf(stderr, "%s\n", list[index]->d_name);
    closedir(dir);
}

int
main(void)
{
    printSortedNames("/home/iharob");
    return 0;
}

1Remember to limit the value of count to the maximum number of pointers you allocated space for.

Community
  • 1
  • 1
Iharob Al Asimi
  • 52,653
  • 6
  • 59
  • 97
  • I have added the line you mentioned, however the value first_dirent is NULL. I seem to be having an issue with the value getting passed into the compare function – VMA92 Sep 27 '15 at 19:15
  • Good point about the size. I will try with count and see how that goes. – VMA92 Sep 27 '15 at 19:28
  • @VMA92 Please limit the `readdir()` loop to `5` (the same value at `malloc(5 * sizeof(struct dirent *))` meanwhile to test, otherwise you cannot predict the result of your program. Also, you casting `malloc()` and using `dirent` without `struct` suggest you are using a c++ compiler. If that's true retag your question and let me delete the answer, because c++ programmers would never use this functions for this tasks I think. – Iharob Al Asimi Sep 27 '15 at 19:31
  • It seems as only the value of const void* a gets passed into the comparison function. When casting the second value const void* b the result is NULL. – VMA92 Sep 27 '15 at 19:43
  • What are you comparing in the comparison funcion? – Iharob Al Asimi Sep 27 '15 at 19:59
  • I am trying to compare to dirent structs. I have not created my comparison yet I am first trying to make sure my two dirents to be compared are passed through properly. The first parameter a that gets casted gives me the first entry of my array, however when I cast the second parameter b, I get NULL for some weird reason – VMA92 Sep 27 '15 at 20:03
  • @VMA92 Try my sample code, let me know if it helped. – Iharob Al Asimi Sep 27 '15 at 20:04
  • This gave me the same value for both a and b in the compareSize method. – VMA92 Sep 27 '15 at 20:27
  • Why do you care? Did it sort the files correctly? That could happen indeed, don't worry about it. – Iharob Al Asimi Sep 27 '15 at 20:30
  • it seems to work on a linux platform but not on visual studio oddly enough. I will continue to test on linux and see how it goes – VMA92 Sep 27 '15 at 21:04
  • I can't tell you why it fails on Windows because I don't like it and I don't know much about Visual C Compiler. – Iharob Al Asimi Sep 27 '15 at 21:08
3

Your sizeCompare function is not returning anything. You need to implement is such that it returns -1 when a < b, 0 when a = b and 1 when a > b.

int sizeCompare(const void* a, const void* b) {
    // The implementation should return something
}

Also, you're calculating the file_list_size incorrectly. Can't you pass count as the second argument instead?

qsort(file_list, count, sizeof(struct dirent), sizeCompare);
John Bupit
  • 10,406
  • 8
  • 39
  • 75