C编程:将目录文件名写入数组,出现奇怪的符号

问题描述 投票:-2回答:4

我正在尝试将目录中每个文件的名称放入一个数组中。在我打印数组本身之前,代码似乎运行良好。

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

int main ()
{
    char directory_name[10];
    DIR *ptr;
    int n, i;

    n = 0;
    i = 0;

    //Ask user for directory name
    struct dirent *directory;
    printf ("Enter Directory Name:\t");
    scanf ("%s", directory_name);
    ptr = opendir (directory_name);
    printf ("\nDirectory %s\n", directory_name);

    while ((directory = readdir (ptr)) != NULL) {
        if (!strcmp (directory->d_name, ".")
            || !strcmp (directory->d_name, "..")) {

        } else {
            n++;
        }
    }

    rewinddir (ptr);

    char *filesList[n];

    //Put file names into the array
    while ((directory = readdir (ptr)) != NULL) {
        if (!strcmp (directory->d_name, ".")
            || !strcmp (directory->d_name, "..")) {
        } else {
            filesList[i] = (char *) malloc (strlen (directory->d_name) + 1);
            strncpy (filesList[i], directory->d_name,
                    strlen (directory->d_name));
            i++;
        }
    }
    rewinddir (ptr);

    for (i = 0; i <= n; i++) {
        printf ("%s\n", filesList[i]);
    }

    closedir (ptr);
    return 0;
}

filesList打印,但在打印输出结束时,它会添加另一行以及这些符号:

)─ìD$â└┴Φ┴αëE╨Θï

我想从数组中删除这些符号。但是,由于我不知道它们是什么或它们来自哪里,我不知道从哪里开始。我是C的新手,所以如果这是一个初学者问题我会道歉。

我想澄清一下,程序将这些符号识别为文件名,即使目录中没有该名称的文件。索引会像文件一样增加。

c arrays file
4个回答
1
投票

不要在代码中使用幻数:

char directory_name[10];

如果您需要常量,请定义一个常量,或者如果已为特定应用程序提供了适当的常量,请使用该常量,例如,

#ifndef PATH_MAX        /* declare constants as required */
#define PATH_MAX 4096
#endif

#define NFILE 128
...
char directory_name[PATH_MAX] = "",
    ...

您不能完全确定任何问题的根源在哪里,因为您无法验证任何所需的返回。除void之外的所有函数都提供可用于表示成功或失败的返回 - 使用它们,例如

    /* open and VALIDATE directory with opendir */
    ptr = opendir (directory_name);
    if (!ptr) {
        fprintf (stderr, "error: directory open failed '%s'.\n", 
                directory_name);
        return 1;
    }
    printf ("\nDirectory open '%s'\n", directory_name);

这是用户输入的绝对必要条件。

填充的数组索引介于0n-1之间,因此for (i = 0; i <= n; i++)的索引通过读取超出VLA界限的方式调用Undefined Behavior。使用for (i = 0; i < n; i++)

而不是在你的目录上进行两次传递(第一次计算文件数量,倒带,第二次读取文件名),只需在最初为char分配一些合理数量的指针,跟踪你填写的数字,以及达到目前的限制时,realloc。以下代码就是这样做的(以及清理用户输入 - 应该通过使用fgets而不是scanf来简化并使其更加健壮),例如:

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

#ifndef PATH_MAX        /* declare constants as required */
#define PATH_MAX 4096
#endif

#define NFILE 128

/* simple function to empty stdin.
 * mandatory when taking user input with scanf
 */
void empty_stdin()
{
    int c = getchar();

    while (c != '\n' && c != EOF)
        c = getchar();
}

int main (void) {

    DIR *ptr = NULL;
    struct dirent *directory = NULL;
    char directory_name[PATH_MAX] = "",
        **files = NULL;     /* using pointer to pointer to char */
    size_t i, n = 0, nptr = NFILE;

    /* prompt for and VALIDATE input of directory_name */
    for (;;) {     /* loop until valid input received or user cancels */
        int rtn;
        printf ("Enter Directory Name: ");
        rtn = scanf ("%s", directory_name);
        if (rtn == 1) {     /* good input, proceed */
            empty_stdin();
            break;
        }
        else if (rtn == EOF) {  /* user canceled input */
            fprintf (stderr, "user canceled input.\n");
            return 1;
        }   /* handle other error */
        fprintf (stderr, "error: invalid input.\n");
        empty_stdin();
    }

    /* open and VALIDATE directory with opendir */
    ptr = opendir (directory_name);
    if (!ptr) {
        fprintf (stderr, "error: directory open failed '%s'.\n", 
                directory_name);
        return 1;
    }
    printf ("\nDirectory open '%s'\n", directory_name);

    /* allocate and VALIDATE nptr pointer for filenames */
    files = calloc (nptr, sizeof *files);
    if (!files) {
        perror ("pointer allocation failed");
        return 1;
    }

    /* read each filename, allocate/VALIDATE storage, copy to files[n],
     * check total pointer allocation, realloc as required.
     */
    while ((directory = readdir (ptr)) != NULL) {
        if (!strcmp (directory->d_name, ".")    /* skip dot files */
            || !strcmp (directory->d_name, ".."))
            continue;
        /* allocate VALIDATE storage for filename */
        files[n] = malloc (strlen (directory->d_name) + 1);
        if (!files[n]) {
            perror ("memory exhausted - filesList");
            return 1;
        }
        strcpy (files[n++], directory->d_name); /* copy filename */
        /* check if ptr limit reached, if so realloc 2x pointers */
        if (n == nptr) {
            void *tmp = realloc (files, nptr * 2 * sizeof *files);
            if (!tmp) {
                perror ("realloc failed - files");
                break;  /* files still points to original block */
            }
            files = tmp;
            /* optional - zero all newly added memory */
            memset (files + nptr, 0, nptr * sizeof *files);
            nptr *= 2;  /* increment number currently allocated */
        }
    }

    for (i = 0; i < n; i++) {   /* indexes are 0 -> n-1 */
        printf ("%s\n", files[i]);
        free (files[i]);        /* don't forget to free memory */
    }
    free (files);   /* free pointers */

    closedir (ptr);

    return 0;
}

示例使用/输出

$ ./bin/readdir_alloc
Enter Directory Name: dat
Directory open 'dat'
lastchgcol.txt
arrinpt.txt
...

内存使用/错误检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块都有2个职责:(1)始终保留指向内存块起始地址的指针,因此,(2)当它为no时可以释放它需要更久。

对于Linux,valgrind是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

$ valgrind ./bin/readdir_alloc
==10547== Memcheck, a memory error detector
==10547== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==10547== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==10547== Command: ./bin/readdir_alloc
==10547==
Enter Directory Name: dat
Directory open 'dat'
lastchgcol.txt
arrinpt.txt
...
==10547==
==10547== HEAP SUMMARY:
==10547==     in use at exit: 0 bytes in 0 blocks
==10547==   total heap usage: 248 allocs, 248 frees, 38,729 bytes allocated
==10547==
==10547== All heap blocks were freed -- no leaks are possible
==10547==
==10547== For counts of detected and suppressed errors, rerun with: -v
==10547== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存并且没有内存错误。

使用fgets是获取用户输入的推荐方法。 scanf如果充满了陷阱新C程序员的陷阱。 fgets只需要检查返回值,然后用nul-terminating字符覆盖缓冲区中包含的尾随'\n'。你可以删除整个循环的for循环,直到使用scanf接收到有效输入,使用fgets使用以下简化代码:

    size_t len;
    printf ("Enter Directory Name: ");
    if (!fgets (directory_name, PATH_MAX, stdin)) {
        fprintf (stderr, "error: failed to read directory_name.\n");
        return 1;
    }
    len = strlen (directory_name);
    if (len && directory_name[len - 1] == '\n')
        directory_name[--len] = 0;
    else if (len + 1 == PATH_MAX) {
        fprintf (stderr, "error: directory_name too long.\n");
        return 1;
    }

仔细看看,如果您有其他问题,请告诉我。


0
投票

这条线

strncpy (filesList[i],directory->d_name, strlen(directory->d_name) );\

将复制名称没有终止NUL,所以当你以后打印它时,你会在字符串的末尾看到额外的垃圾。请改用strcpy。更好的是,使用strdup一步分配和复制字符串。


0
投票

在末尾附加一个终止空字符。您可以使用strdup()来避免这种情况。


0
投票

声明char *filesList[n]; init指向NULL filesList[n] = NULL;的最后一个指针然后将你的循环更改为此

for(i=0; filesList[i]; i++) {
   printf("%s\n", filesList[i]);
}

你有那个垃圾,因为你试图访问指向你没有初始化的内存的某些部分的指针。例如,您有目录Music /,其中包含四个文件,file1,file2,file3,file4。所以你的初始化将类似于:

filesList[0] file4 copy
filesList[1] file2 copy
filesList[2] file1 copy
filesList[3] file3 copy
filesList[4] -> point to some uninit part of memory

然后你在循环打印filesList [4],它显示垃圾。

在这样的陈述中,最好使用continue而不是空体:

if (!strcmp(directory->d_name, ".") || !strcmp(directory->d_name, ".."))
{
    continue;
}

而且你也可以用strncpy()替换snprintf()它也总是包含空终止符'\0',除非缓冲区大小为0但你可以检查它或只是手动添加'\0'

filesList[i][strlen(directory->d_name)] = '\0';
© www.soinside.com 2019 - 2024. All rights reserved.