Функция C 'String Array' возвращает пустое значение

Я пишу эту программу для перечисления каждого файла в каталоге, чтобы попрактиковаться в использовании указателей, поскольку я новичок в этой концепции и языке C. Оператор return для **getFiles() по какой-то причине возвращается пустым. Как ни странно, функция getFiles(), когда я печатаю свой «массив строк» ​​(каталоги), печатает 1 из 3 файлов, которые есть в моем каталоге. Однако когда я печатаю результаты в main(), ничего не происходит.

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

int main(){
    char **getFiles();
    printf("\nFUNCTION RETURN:\n%s", *getFiles());
    return 0;
}
#define BUFFER_SIZE 4096

char **getFiles(){

    char **dirs;

    int total = 10; //Need to do this
    dirs = malloc(total*sizeof(char *));

    char buffer[BUFFER_SIZE];

    struct dirent *de;  
  
    DIR *dr = opendir("."); 
  
    if (dr == NULL)  
    { 
        printf("Error"); 
        return 0; 
    } 
  
    int x=0;
    while ((de = readdir(dr)) != NULL){ 
            int length = strlen(de->d_name);
            dirs[x] = malloc(length*sizeof(char));
            strcpy(buffer, de->d_name);
            strcpy(dirs[x], buffer);
            dirs[x] = buffer;
            x++;
    }

    closedir(dr);     
    printf("\nPRINT DIRS\n:\n%s", *dirs);
    return dirs;
    for(int i =0; i<x;i++){
        free(dirs[i]);
    }
    free(dirs);
}

Как я уже сказал, я все еще новичок во всей этой штуке с указателями и адресами, поэтому я попробовал отменить ссылку на кучу вещей и распечатать это, но ничего не помогло.

Код после return dirs; никогда не будет выполнен. dirs[x] = buffer; присваивает указатель на локальную переменную dirs[x]. Как только функция возвращает значение, локальная переменная уничтожается, оставляя висячий указатель на недопустимый адрес памяти. Это неопределенное поведение. Здесь есть и другие ошибки, например, недостаточно места для завершающего 0 и странного танца strcpy. и никакой проверки, чтобы убедиться, что x никогда не превышает 9.

Retired Ninja 19.05.2024 04:40

Если вы ошибаетесь, например ожидаете, что коды будут выполняться после return dirs;, вы, вероятно, еще не готовы изучать указатели. Прежде чем углубляться, изучите основы. Также я надеюсь, вы понимаете, что делает оператор char **getFiles(); в main. В частности, он ничего не выполняет.

Weijun Zhou 19.05.2024 04:46

Вы храните указатели на buffer в своем массиве dirs. Но buffer является локальным для getFiles, поэтому после возврата getFiles он больше не действителен. Это неопределенное поведение. И, как уже упоминалось, код после вашего оператора return, очевидно, никогда не будет доступен, так что это мертвый код, который бесполезен. Переместите его в другое место.

Tom Karzes 19.05.2024 05:39

ОТ: Пожалуйста, обратитесь к «программе», или «коду», или, возможно, «функции». На просторечии. «Сценарий» имеет значение, неприменимое к исходному коду C. Просто к вашему сведению... :-)

Fe2O3 24.05.2024 01:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
169
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Первый :

char **getFiles();

Это неправильно. Где имя переменной?

char **my_files = getFiles();

Вот хороший способ сделать это.

int length = strlen(de->d_name);
dirs[x] = malloc(length*sizeof(char));
strcpy(buffer, de->d_name);
strcpy(dirs[x], buffer);
dirs[x] = buffer;

Почему бы просто не использовать strdup()?

return dirs;
for(int i =0; i<x;i++){
    free(dirs[i]);
}
free(dirs);

Весь код после return не будет выполняться.

Вот что вам поможет: Введение и справочное руководство по языку GNU C

И вот что-то... (надеюсь, вы этим не воспользуетесь, если вы этого не понимаете):

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

#define TOTAL_DIR 10

char **
getFiles(void)
{
    char             **dirs;
    struct dirent    *de;
    DIR              *dr;

    dr = opendir(".");
    if (dr == NULL)
    {
        printf("Error\n");
        return (0);
    }

    dirs = calloc(TOTAL_DIR, sizeof(char *));
    if (dirs == NULL)
    {
        printf("Error\n");
        return (0);
    }

    int x = 0;
    de = readdir(dr);
    while (de != NULL)
    {
        dirs[x] = strdup(de->d_name);
        x++;
        de = readdir(dr);
    }
    closedir(dr);
    return (dirs);
}

int
main(void)
{
    char **files;

    files = getFiles();
    printf("Files : \n");
    for (int i = 0; i < TOTAL_DIR; i++)
    {
        if (files[i] != NULL)
            printf("%s\n", files[i]);
        free(files[i]);
    }
    free(files);
    return (0);
}

1) ОП неправильно разместил объявление прямой функции внутри main(). Это не неправильно, но и нетрадиционно. 2) Ваша программа будет работать неправильно, если читаемый каталог содержит >10 записей... программа обращается к памяти за пределами dirs[ 9 ]... UB ожидает... Основная проблема: return (0); должно быть return NULL;... Небольшая ошибка: return не является функцией. Неуместные скобки являются нетрадиционными.

Fe2O3 23.05.2024 10:54

Это то, к чему я в конечном итоге пришел, в основном основываясь на кратком ответе от того, что я читаю, и на дополнительных исследованиях, которые я провел на основе информации, которую он мне дал. Спасибо за ваше время и ваши ответы :)

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

char** list_files(const char *path, int *count) {
    struct dirent *entry;
    DIR *dp = opendir(path);

    if (dp == NULL) {
        perror("opendir");
        return NULL;
    }

    *count = 0;
    while ((entry = readdir(dp)) != NULL) {
        (*count)++;
    }
    rewinddir(dp);

    char **files = malloc((*count) * sizeof(char*));
    if (files == NULL) {
        perror("malloc");
        closedir(dp);
        return NULL;
    }

    int index = 0;
    while ((entry = readdir(dp)) != NULL) {
        files[index] = strdup(entry->d_name);
        if (files[index] == NULL) {
            perror("strdup");
            for (int i = 0; i < index; i++) {
                free(files[i]);
            }
            free(files);
            closedir(dp);
            return NULL;
        }
        index++;
    }

    closedir(dp);
    return files;
}

По запросу я предоставлю больше своих мыслей и объяснений моего решения:

Для начала я удалил функцию int main(), чтобы поместить эту функцию в библиотеку, потому что понял, что мне, вероятно, понадобится что-то подобное для будущего проекта. Затем я добавил параметры пути (хранящиеся в «строке») и указатель на число int. Я использовал здесь указатель, потому что мне все еще нужно получить доступ к этому числу в скрипте, используя эту библиотеку. Указатель позволяет нам изменять значение переменной по ее адресу в памяти (что нам понадобится для освобождения памяти в скрипте с помощью библиотеки). В операторе if if (dp == NULL) я изменил printf("Error"); на использование perror() главным образом потому, что вы не можете использовать printf(""); в библиотеке. Подробнее об этом можно прочитать здесь. Я также изменил strcpy(buffer, de->d_name); на files[index] = strdup(entry->d_name);, поскольку strdup() выделяет память для новой строки в куче, что позволяет нам использовать ее в другой функции.

Почетное упоминание @Dúthomhas за подробное описание моего вопроса, несмотря на то, что я уже ответил на него.

Вы должны подчеркнуть, где вы выделяете указатели и где вы выделяете хранилище для строк. Помогаем пользователю понять, что выделение переменной type** — это двухэтапный процесс: выделение памяти для указателей, а затем выделение памяти для каждой строки, назначение начального адреса для выделенного блока, удерживающего строку до следующего неиспользуемого указателя в выделенном блоке. указатели.

David C. Rankin 23.05.2024 02:09

Благодарим вас за вклад в сообщество Stack Overflow. Возможно, это правильный ответ, но было бы очень полезно предоставить дополнительные пояснения к вашему коду, чтобы разработчики могли понять ваши рассуждения. Это особенно полезно для новых разработчиков, которые не так хорошо знакомы с синтаксисом или пытаются понять концепции. Не могли бы вы отредактировать свой ответ, включив в него дополнительную информацию на благо сообщества?

Jeremy Caney 23.05.2024 02:13

Что произойдет, если другой процесс добавит несколько записей ПОСЛЕ того, как эта функция подсчитала старое количество, но ДО того, как эта функция заполнит свой массив? Переполнение буфера... Этот ответ, как таковой, не является надежным...

Fe2O3 24.05.2024 01:02

Подумаем еще немного... Если другой процесс УДАЛИТ записи в течение критического интервала, эта функция вернет неправильный счетчик через *count и оставит указатели мусора (из-за malloc(), а не calloc()), которые будет использовать вызывающий... Программа вылетает.. .

Fe2O3 24.05.2024 01:12

@Fe2O3 Привет! Спасибо за ваш отзыв. Мне просто интересно, как можно эффективно протестировать что-то подобное.

Jacob Thevenot 24.05.2024 03:24

@JacobThevenot Возвращайся завтра. Я подумываю о том, чтобы опубликовать сообщение для SO, вызванное этими вопросами и ответами... (Приложу все усилия, чтобы избежать "токсичной" атмосферы... Не нужно выходить и покупать йод и пластыри...) Если оно будет опубликовано, оно должно быть опубликовано. появится на правой боковой панели в разделе «Связанные вопросы»... Теперь я могу приступить к работе... Ура!... Сейчас очень грубый обходной путь: используйте calloc(), чтобы учесть меньше записей, чем посчитано. (звонящий берет на себя ответственность) и используйте while ( index < *count && (entry = readdir(dp)) != NULL), чтобы учесть больше записей, чем было подсчитано 3 мс назад...

Fe2O3 24.05.2024 03:36
Ответ принят как подходящий

Еще один ответ, надеюсь, более полный.

Если вы собираетесь прочитать список имен каталога, сделайте это все сразу. Содержимое каталога может измениться в любой момент, даже когда вы читаете его содержимое! Таким образом, чем быстрее вы сможете пройти через это, тем больше вероятность того, что список будет правильным.

Вы можете отслеживать изменения в каталоге, чтобы убедиться, что все сделано правильно, но об этом я упомяну здесь лишь вскользь. Вот ссылка, которую можно быстро найти в Google, чтобы узнать больше: Как правильно использовать inotify?

Получить содержимое каталога

Чтобы собрать только список имен относительно данного каталога (используя шаблон кода):

// getFileNames()
// freeFileNames()
// countFileNames()

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

#include <sys/types.h>
#include <dirent.h>

char **getFileNames(const char *dirname){

    // Our final result will be a dynamically-allocated list
    // of dynamically-allocated filenames (relative to dirname).
    // The list ends with a NULL filename.
    char **filenames = NULL;

    // We wish to make a single pass through the directory, so we
    // will use a linked list to collect filenames AND count them.
    struct node {char *filename; struct node *next;};
    struct node * head = NULL;
    size_t count = 0;

    // Scan through the directory,
    // pushing each new filename to the head of the list
    DIR *dir = opendir(dirname);
    if (!dir) return NULL;

    struct dirent *de;
    while ((de = readdir(dir))){
        struct node *node = malloc(sizeof(struct node));
        if (!node) goto lerror;
        node->filename = strdup(de->d_name);
        node->next = head;
        head = node;
        count += 1;
    }
    closedir(dir);

    // Now allocate the resulting array
    // and move all the collected filenames to it,
    // dismantling the linked list as we go
    filenames = malloc((count+1) * sizeof(char*));
    if (!filenames) goto lerror;
    filenames[count] = NULL;

    while (head){
        struct node *next = head->next;
        filenames[--count] = head->filename;
        free(head);
        head = next;
    }
    return filenames;

lerror:
    // If something went wrong, we'll need to clean up
    while (head){
        struct node *next = head->next;
        free(head->filename);
        free(head);
        head = next;
    }
    return NULL;
}

char **freeFileNames(char **filenames){
    // Free each filename
    for (size_t n = 0;  filenames[n];  n++)
        free(filenames[n]);
    // And free the list
    free(filenames);
    return NULL;
}

size_t countFileNames(char **filenames){
    size_t count = 0;
    while (filenames[count])
        count += 1;
    return count;
}

Обратите внимание: я изменил функцию, чтобы она принимала в качестве аргумента путь (относительный или абсолютный) к каталогу, который вы хотите просмотреть. Это хорошая практика! Позвольте вызывающей стороне определить, какой каталог мы хотим прочитать, без необходимости сначала менять CWD, а затем восстанавливать его.

Поскольку функция не возвращает количество файлов в списке, сам список завершается строкой NULL. Это обычная конструкция в C при работе со списками такого типа.

⟶ For example, main()’s argc is not really anything more than a convenience, because argv ends with a NULL string! That is to say, argv[argc] == NULL!

И еще раз важно отметить, что возвращаемый список имен файлов относится к каталогу аргумента. Вы можете вернуть полный путь к каждому файлу (== абсолютное имя пути). Для этого потребуется помощь в объединении имени каталога и имени файла перед добавлением их в результирующий список.

Или вы можете оставить все как есть и просто указать полный путь к файлу, когда он вам понадобится позже. Для этого достаточно легко написать небольшую функцию:

#include <linux/limits.h>
#include <string.h>

char *JoinDirAndFileName(const char *dirname, const char *filename){
    char path[PATH_MAX] = "";
    strcpy(path, dirname);
    strcat(path, "/");
    strcat(path, filename);
    return strdup(path);
}

Это, конечно, требует, чтобы аргумент strdup() сам был полным именем пути, лол.

Это каталог?

Дополнительную полезную информацию о файлах можно получить с помощью семейства функций stat(). Например, нам может быть интересно узнать, относится ли имя файла к каталогу или нет:

#include <stdbool.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

bool isDirectory(const char *filename){
    struct stat sb;
    if (stat( filename, &sb )) return false;  // Failure == not a directory
    return (sb.st_mode & S_IFMT) == S_IFDIR;
}

Имейте в виду, что для этого требуется либо:

  • dirname — абсолютный путь к файлу (в нашем случае это не так), или
  • filename является относительным, что означает, что файл находится в текущем рабочем каталоге (в нашем случае это так и есть)

Сортировка имен файлов

Возможно, вы захотите использовать эту информацию для таких действий, как сортировка имен файлов.

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

int FileNameCompare(const void *a, const void *b){
    // Sort directory names before filenames
    // Then sort using strcmp()
    bool is_dir_a = isDirectory(*(const char **)a);
    bool is_dir_b = isDirectory(*(const char **)b);
    if (is_dir_a == is_dir_b) 
        return strcmp(*(const char **)a, *(const char **)b);
    return is_dir_a ? -1 : 1;
}

void SortFileNames(char **filenames){
    qsort(filenames, countFileNames(filenames), sizeof(char *), FileNameCompare);
}

Опять же, сортировка работает только в том случае, если filename получает имя файла, которое он может использовать isDirectory(), то есть либо

  • список имен файлов представляет собой список абсолютных путей к файлам или
  • имена файлов в списке указаны относительно CWD. Для нашего примера программы верно последнее.

Пример программы

Теперь мы можем собрать все это вместе в качестве примера программы:

#include <stdio.h>

int main(void){

    // Get the list of filenames in the CWD
    char **filenames = getFileNames(".");
    if (!filenames)
    {
        fprintf( stderr, "%s\n", "fooey!" );
        return 1;
    }

    // Might as well sort them
    SortFileNames(filenames);

    // Print the list
    puts("FILENAMES:");
    for (size_t n = 0;  filenames[n];  n++)
        if (isDirectory(filenames[n]))
            printf("  %s/\n", filenames[n]);
        else
            printf("  %s\n", filenames[n]);

    // Clean up
    filenames = freeFileNames(filenames);
    return 0;
}

Чтобы скомпилировать и запустить пример программы, просто объедините все блоки кода в текстовый файл и сохраните его как stat(), а затем скомпилируйте его примерно так:

$ clang -Wall -Wextra -O3 dirlist.c -o dirlist
$ ./dirlist
FILENAMES:
  ./
  ../
  dirlist
  dirlist.c
$

Windows ≠ Linux

Увы, пользователи Windows, для получения содержимого каталога требуются разные функции, но все работает примерно одинаково. Вы можете прочитать больше, начиная с удобной документации Microsoft: FindFirstFile() , FindNextFile() , FindClose() и других функций управления файлами.

Хороший код. Маленькая мелочь... Каждое сравнение, сделанное qsort(), будет выполнять 2 системных вызова для отделения каталогов от обычных файлов. Затем еще один системный вызов для каждой записи при печати... Кажется неоптимальным, когда принадлежность к одному из двух видов может быть определена и занесена в заметку во время сканирования... Просто мысль... Ура!

Fe2O3 23.05.2024 10:07

@ Fe2O3 Да, в этом отношении это явно неоптимально. (На самом деле я думал, что все было еще хуже, лол.) Лично я хранил больше информации о каждой записи, чем просто имя, но я решил, что этого достаточно, чтобы справиться с простым перебором каталога. Рад тебя видеть! 🙂

Dúthomhas 23.05.2024 10:15

@Fe2O3 В Linux этих системных вызовов можно избежать, используя данные из поля d_type структуры dirent, при условии, что базовая файловая система поддерживает поле d_type.

Andrew Henle 23.05.2024 20:03

@AndrewHenle В руководстве ясно указано, что даже если d_type существует в структуре, он может не содержать полезной информации в зависимости от файловой системы пользователя. Абсолютно стоит потратить время на его проверку и использование, если оно доступно, но я не склонен делать этот ответ чем-то большим, чем прямой ответ на заданный вопрос, включая отказ от изменения ответа данных на что-то иное, чем структура char * array[] ОП. Я не искал, есть ли еще один вопрос SO по чтению каталога, который прямо спрашивает «как читать каталог», но предложения по действиям по этому поводу приветствуются.

Dúthomhas 23.05.2024 20:34

@Dúthomhas Привет назад! 🙂 Я поднимаю свою проблему, потому что (по моему мнению) определение типа записи во время сканирования было бы на полпути к разработке (задача ОП, а не ваша!) Добавления рекурсивного спуска структуры каталогов... Ваш ответ VG на ОП проблема (УФ)... Просто упоминаю кое-что, о чем ОП должен подумать... Ура! :-)

Fe2O3 24.05.2024 00:46

Другие вопросы по теме