2

I need to use readdir_r() to read the contents of a directory in a multithreaded program. Since the size of struct dirent is filesystem dependent, man readdir_r recommends

name_max = pathconf(dirpath, _PC_NAME_MAX);
if (name_max == -1)                     /* Limit not defined, or error */
    name_max = 255;                     /* Take a guess */
len = offsetof(struct dirent, d_name) + name_max + 1;

to find the size of the allocation needed. To allocate it

entryp = malloc(len);

is called, and finally readdir_r() uses it like this:

struct dirent *returned;
readdir_r(DIR*, entryp, &returned);

However, I'd like to avoid calling malloc() (or any other manual memory management function).

One way I've thought of is

_Alignas(struct dirent) char direntbuf[len];
struct dirent *entryp = (struct dirent*) direntbuf;

This should give a correctly aligned allocation, but it violates strict aliasing. However, the buffer is never accessed via a char* so the most likely problem, the compiler reordering accesses to the buffer via different types, cannot occur.

Another way could be by alloca(), which returns a void*, avoiding strict aliasing problems. However, alloca() does not seem to guarantee alignment the way malloc() and friends do. To always get an aligned buffer, something like

void *alloc = alloca(len + _Alignof(struct dirent));
struct dirent *direntbuf = (struct dirent*)((uintptr_t)&((char*)alloc)[_Alignof(struct dirent)]&-_Alignof(struct dirent));

would be needed. In particular, the cast to char * is needed to perform arithmetic on a pointer, and the cast to uintptr_t is needed to do the binary &. This doesn't look more well-defined than allocating a char[].

Is there a way to avoid manual memory management when allocating a struct dirent?

alk
  • 69,737
  • 10
  • 105
  • 255
EOF
  • 6,273
  • 2
  • 26
  • 50
  • For <=C99: Calculate `len` as per your answer, then define `char buffer[len] `. – alk May 09 '15 at 10:44
  • @alk: `char buffer[len]` is not necessarily correctly aligned for a `struct dirent`. Also, dereferencing an object of type `char` via a pointer of type `struct dirent` is undefined behaviour according to C. – EOF May 09 '15 at 11:22
  • Are you this "*but it violates strict aliasing*" holds for a `char`-array? – alk May 09 '15 at 13:14
  • @alk. Well, C11 draft standard section 6.5 says that you can only access an object via a pointer to the type of that object or through a pointer to a character type. But if the object is already a `char`, that doesn't help here. – EOF May 09 '15 at 13:57
  • Ahok, this unfortunately does not hold the other way round ...:-S – alk May 09 '15 at 14:02

2 Answers2

3

What about defining this:

#include <stddef.h> /* For offsetof */
#include <dirent.h>


union U
{
  struct dirent de;
  char c[offsetof(struct dirent, d_name) + NAME_MAX + 1]; /* NAME_MAX is POSIX. */
};
alk
  • 69,737
  • 10
  • 105
  • 255
0

The readdir_r function signature is:

int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);

And direct is a struct like this:

struct dirent {
    ino_t          d_ino;       /* inode number */
    off_t          d_off;       /* offset to the next dirent */
    unsigned short d_reclen;    /* length of this record */
    unsigned char  d_type;      /* type of file; not supported
                                   by all file system types */
    char           d_name[256]; /* filename */
};

You have to pass a pointer to readdir_r but how you allocate memory for the dirent structure is entirely up to you.

You could do it like this and use a stack variable.

struct dirent entry = {0};
...
readdir_r(DIR*, &entry, &returned);
Angus Comber
  • 9,316
  • 14
  • 59
  • 107