Выделение структуры dirent без malloc()

Мне нужно использовать readdir_r() для чтения содержимого каталога в многопоточной программе. Поскольку размер struct dirent зависит от файловой системы, man readdir_r рекомендует

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;

чтобы найти размер необходимого распределения. Чтобы выделить его

entryp = malloc(len);

вызывается, и, наконец, readdir_r() использует его следующим образом:

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

Однако я хотел бы избежать вызова malloc() (или любой другой функции ручного управления памятью).

Один из способов, о котором я подумал, это

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

Это должно дать правильно выровненное выделение, но нарушает строгое сглаживание. Однако к буферу никогда не обращаются через char*, поэтому наиболее вероятная проблема, когда компилятор переупорядочивает доступ к буферу через разные типы, не может возникнуть.

Другим способом может быть alloca(), который возвращает void*, избегая проблем строгого алиасинга. Однако alloca(), похоже, не гарантирует выравнивания, как это делают malloc() и его друзья. Чтобы всегда получать выровненный буфер, что-то вроде

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

будет необходимо. В частности, приведение к char * необходимо для выполнения арифметических действий над указателем, а приведение к uintptr_t необходимо для выполнения двоичного &. Это не выглядит более четко определенным, чем выделение char[].

Есть ли способ избежать ручного управления памятью при выделении struct dirent?


person EOF    schedule 09.05.2015    source источник
comment
Для ‹=C99: рассчитайте len в соответствии с вашим ответом, затем определите char buffer[len] .   -  person alk    schedule 09.05.2015
comment
@alk: char buffer[len] не обязательно правильно выровнен для struct dirent. Кроме того, разыменование объекта типа char с помощью указателя типа struct dirent является неопределенным поведением согласно C.   -  person EOF    schedule 09.05.2015
comment
Вы это но это нарушает строгие правила псевдонимов для char-массива?   -  person alk    schedule 09.05.2015
comment
@говорить. Что ж, в разделе 6.5 проекта стандарта C11 говорится, что вы можете получить доступ к объекту только через указатель на тип этого объекта или через указатель на тип символа. Но если объект уже является char, это здесь не поможет.   -  person EOF    schedule 09.05.2015
comment
Ахок, это к сожалению не наоборот...:-S   -  person alk    schedule 09.05.2015


Ответы (2)


Как насчет определения этого:

#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. */
};
person alk    schedule 09.05.2015
comment
Вау, это на самом деле очень умно... Меня впечатляет такое использование союза. - person EOF; 09.05.2015
comment
@jongware: Как здесь может помочь структура? - person alk; 10.05.2015
comment
То же, что и вы предлагаете, но без offsetof штучки. - person Jongware; 10.05.2015
comment
Сорри, а я тебя не понял? @Jongware - person alk; 14.05.2015
comment
Только 1_. Я не понимаю, почему вы решили вместо этого создать союз. - person Jongware; 14.05.2015
comment
@Jongware: Ах! Хорошая мысль :-), но как сделать так, чтобы компилятор не добавил отступы между de и c? - person alk; 14.05.2015
comment
@Jongware: если бы применялось заполнение, использовались бы эти байты. Разве это не вызовет UB? - person alk; 14.05.2015

Сигнатура функции readdir_r:

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

А direct — это такая структура:

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 */
};

Вы должны передать указатель на readdir_r, но то, как вы выделяете память для структуры dirent, полностью зависит от вас.

Вы можете сделать это так и использовать переменную стека.

struct dirent entry = {0};
...
readdir_r(DIR*, &entry, &returned);
person Angus Comber    schedule 09.05.2015
comment
Это невыносимо, так как только в Linux d_name набирается char[256]. - person alk; 09.05.2015