0

I have a program where I'm trying to use scandir(), but running into an issue that is raising a few questions for me.

According to the man-page for scandir(), the function takes in a "triple pointer" to a dirent struct:

int scandir(const char *dirp, struct dirent ***namelist,
              int (*filter)(const struct dirent *),
              int (*compar)(const struct dirent **, const struct dirent **));

However, the page also provides the example below:

#define _DEFAULT_SOURCE
       #include <dirent.h>
       #include <stdio.h>
       #include <stdlib.h>

       int
       main(void)
       {
           struct dirent **namelist;
           int n;

           n = scandir(".", &namelist, NULL, alphasort);
           if (n == -1) {
               perror("scandir");
               exit(EXIT_FAILURE);
           }

           while (n--) {
               printf("%s\n", namelist[n]->d_name);
               free(namelist[n]);
           }
           free(namelist);

           exit(EXIT_SUCCESS);
       }

Notice that they declare a double pointer named namelist and use the "address-of" operator with it in scandir().

I'm assuming this somehow turns it into a "triple pointer"? Hoping someone can explain what's going on here with a diagram or something. I would greatly appreciate it.

GainzNerd
  • 312
  • 1
  • 10
  • The 1st pointer is about pointing to the variable (`dirent` in your example) so `scandir` can assign to it, the 2nd pointer is about allocating an array, and the 3rd pointer is about allocating each element of the array (each element contains a structure with information about the file). The "triple" pointer comes from the ampersand which takes the address of the `namelist` variable. – hgs3 Dec 12 '20 at 23:58
  • That is quite interesting. Not sure how that works, but very interesting. What I'm concerned with, however, is how using the address-of operator turns the double pointer into a triple pointer. – GainzNerd Dec 13 '20 at 00:02
  • The 'address-of' operator gives you back a pointer. Since your variable is declared with two pointers, when you take its address, you get back the 3rd pointer. – hgs3 Dec 13 '20 at 00:05
  • 1
    What do you think a pointer is? The address of an object is a pointer to an object. Given `int x0;`, do you understand what `int *x1 = &x0;` does? What about `int **x2 = &x1;`? Or `int ***x3 = &x2;`? Given any object `q` of type `T`, `&q` is the address of the object and a `T *` is a type of a thing that can hold that address. – Eric Postpischil Dec 13 '20 at 00:06

2 Answers2

0

& in a declaration in C++ means "reference to". Since this is neither C++ nor a declaration, it's irrelevant.

In an expression though, it is the unary address-of operator (or the binary bitwise and operator).

From a type perspective, taking the address of something just adds (another) * to the type. Hence, the address of a struct dirent ** does indeed have type struct dirent ***.

Useless
  • 64,155
  • 6
  • 88
  • 132
0

The purpose of scandir is to retrieve an array or 'list' of dirents.

While there are a few differences between 'pointer-to-first-array-element' and an actual array (which i highly recommend to read about if you intent to use c), we might consider them equal for this case.

As such an array of dirent structures would be struct dirent *namelist (which is not the case).

But the function actually yields an indirect-list, thus an array of pointers to dirent structures: struct dirent **namelist.

This is how namelist is defined in the example and this also explains the access via namelist[n]->d_name. Since namelist is an array -> namelist[n] is an array-element (a pointer to dirent). The 'arrow operator' dereferences this pointer and accesses the field d_name.

That was the easy part, now lets look at the function call. The documentation you linked states

collected in array namelist which is allocated via malloc

This means that somewhere inside of scandir malloc is called and returns a pointer to the allocated memory. However the pointers value must somehow be known to the caller of scandir. One possibility would be to simply return it just as malloc does. But instead it was chosen to use an 'output-parameter'. This simply means a pointer to the variable (meaning its address) that would have received that value if it was a typical 'return-parameter'. This is where the third asterisk (*) comes from. As you know: the addressof-operator (&) just gives that address. This is why &namelist is passed as 'output-argument'.

To maybe better understand output-parameters think of the scanf function. Here also each value that you want to 'scan' is passed as output-parameter to the function. If you want to receive an integer you create an int variable (e.g. int myVal;) and pass it's address (scanf("%d", &myVal);). The expected parameter type would be int *.

Or again speaking generally:

<result-type> resultValue;
<return-type> returnValue = <function>(&resultValue);

where the function with output parameter would be defined as

<return-type> <function>(<result-type> *foo){...}

And yes, the addressof operator adds an asterisk (*) to the type (depending on the type this might be a bit more difficult then just adding the asterisk (e.g. function pointers or pointers to array (actual arrays))). In our case <result-val> is struct dirent ** and the third asterisk is added for the parameter type.

As a last note: the returntype of scandir is the number of elements in the array. Since namelist does not hold the length information of the array in its type, you need the return value to know how many values you can access.

Burdui
  • 1,242
  • 2
  • 10
  • 24