Я пишу эту программу для перечисления каждого файла в каталоге, чтобы попрактиковаться в использовании указателей, поскольку я новичок в этой концепции и языке 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;, вы, вероятно, еще не готовы изучать указатели. Прежде чем углубляться, изучите основы. Также я надеюсь, вы понимаете, что делает оператор char **getFiles(); в main. В частности, он ничего не выполняет.
Вы храните указатели на buffer в своем массиве dirs. Но buffer является локальным для getFiles, поэтому после возврата getFiles он больше не действителен. Это неопределенное поведение. И, как уже упоминалось, код после вашего оператора return, очевидно, никогда не будет доступен, так что это мертвый код, который бесполезен. Переместите его в другое место.
ОТ: Пожалуйста, обратитесь к «программе», или «коду», или, возможно, «функции». На просторечии. «Сценарий» имеет значение, неприменимое к исходному коду C. Просто к вашему сведению... :-)





Первый :
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 не является функцией. Неуместные скобки являются нетрадиционными.
Как сейчас написано, ваш ответ неясен. Пожалуйста, отредактируйте , чтобы добавить дополнительную информацию, которая поможет другим понять, как это относится к заданному вопросу. Более подробную информацию о том, как писать хорошие ответы, вы можете найти в справочном центре.
Это то, к чему я в конечном итоге пришел, в основном основываясь на кратком ответе от того, что я читаю, и на дополнительных исследованиях, которые я провел на основе информации, которую он мне дал. Спасибо за ваше время и ваши ответы :)
#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** — это двухэтапный процесс: выделение памяти для указателей, а затем выделение памяти для каждой строки, назначение начального адреса для выделенного блока, удерживающего строку до следующего неиспользуемого указателя в выделенном блоке. указатели.
Благодарим вас за вклад в сообщество Stack Overflow. Возможно, это правильный ответ, но было бы очень полезно предоставить дополнительные пояснения к вашему коду, чтобы разработчики могли понять ваши рассуждения. Это особенно полезно для новых разработчиков, которые не так хорошо знакомы с синтаксисом или пытаются понять концепции. Не могли бы вы отредактировать свой ответ, включив в него дополнительную информацию на благо сообщества?
Что произойдет, если другой процесс добавит несколько записей ПОСЛЕ того, как эта функция подсчитала старое количество, но ДО того, как эта функция заполнит свой массив? Переполнение буфера... Этот ответ, как таковой, не является надежным...
Подумаем еще немного... Если другой процесс УДАЛИТ записи в течение критического интервала, эта функция вернет неправильный счетчик через *count и оставит указатели мусора (из-за malloc(), а не calloc()), которые будет использовать вызывающий... Программа вылетает.. .
@Fe2O3 Привет! Спасибо за ваш отзыв. Мне просто интересно, как можно эффективно протестировать что-то подобное.
@JacobThevenot Возвращайся завтра. Я подумываю о том, чтобы опубликовать сообщение для SO, вызванное этими вопросами и ответами... (Приложу все усилия, чтобы избежать "токсичной" атмосферы... Не нужно выходить и покупать йод и пластыри...) Если оно будет опубликовано, оно должно быть опубликовано. появится на правой боковой панели в разделе «Связанные вопросы»... Теперь я могу приступить к работе... Ура!... Сейчас очень грубый обходной путь: используйте calloc(), чтобы учесть меньше записей, чем посчитано. (звонящий берет на себя ответственность) и используйте while ( index < *count && (entry = readdir(dp)) != NULL), чтобы учесть больше записей, чем было подсчитано 3 мс назад...
Еще один ответ, надеюсь, более полный.
Если вы собираетесь прочитать список имен каталога, сделайте это все сразу. Содержимое каталога может измениться в любой момент, даже когда вы читаете его содержимое! Таким образом, чем быстрее вы сможете пройти через это, тем больше вероятность того, что список будет правильным.
Вы можете отслеживать изменения в каталоге, чтобы убедиться, что все сделано правильно, но об этом я упомяну здесь лишь вскользь. Вот ссылка, которую можно быстро найти в 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(), то есть либо
Теперь мы можем собрать все это вместе в качестве примера программы:
#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, для получения содержимого каталога требуются разные функции, но все работает примерно одинаково. Вы можете прочитать больше, начиная с удобной документации Microsoft: FindFirstFile() , FindNextFile() , FindClose() и других функций управления файлами.
Хороший код. Маленькая мелочь... Каждое сравнение, сделанное qsort(), будет выполнять 2 системных вызова для отделения каталогов от обычных файлов. Затем еще один системный вызов для каждой записи при печати... Кажется неоптимальным, когда принадлежность к одному из двух видов может быть определена и занесена в заметку во время сканирования... Просто мысль... Ура!
@ Fe2O3 Да, в этом отношении это явно неоптимально. (На самом деле я думал, что все было еще хуже, лол.) Лично я хранил больше информации о каждой записи, чем просто имя, но я решил, что этого достаточно, чтобы справиться с простым перебором каталога. Рад тебя видеть! 🙂
@Fe2O3 В Linux этих системных вызовов можно избежать, используя данные из поля d_type структуры dirent, при условии, что базовая файловая система поддерживает поле d_type.
@AndrewHenle В руководстве ясно указано, что даже если d_type существует в структуре, он может не содержать полезной информации в зависимости от файловой системы пользователя. Абсолютно стоит потратить время на его проверку и использование, если оно доступно, но я не склонен делать этот ответ чем-то большим, чем прямой ответ на заданный вопрос, включая отказ от изменения ответа данных на что-то иное, чем структура char * array[] ОП. Я не искал, есть ли еще один вопрос SO по чтению каталога, который прямо спрашивает «как читать каталог», но предложения по действиям по этому поводу приветствуются.
@Dúthomhas Привет назад! 🙂 Я поднимаю свою проблему, потому что (по моему мнению) определение типа записи во время сканирования было бы на полпути к разработке (задача ОП, а не ваша!) Добавления рекурсивного спуска структуры каталогов... Ваш ответ VG на ОП проблема (УФ)... Просто упоминаю кое-что, о чем ОП должен подумать... Ура! :-)
Код после
return dirs;никогда не будет выполнен.dirs[x] = buffer;присваивает указатель на локальную переменнуюdirs[x]. Как только функция возвращает значение, локальная переменная уничтожается, оставляя висячий указатель на недопустимый адрес памяти. Это неопределенное поведение. Здесь есть и другие ошибки, например, недостаточно места для завершающего 0 и странного танца strcpy. и никакой проверки, чтобы убедиться, что x никогда не превышает 9.