【问题标题】:C writing readdir to char array variable?C将readdir写入char数组变量?
【发布时间】:2018-08-11 23:31:57
【问题描述】:

我正在尝试将目录列表写入 char 数组,但在尝试使用 strcpy 或 strcat 时出现分段错误。有没有更好的方法来解决这个问题?

我只是想修改以下内容以创建一个字符串,而不是打印到 stdout。我猜我只是错过了一些非常简单的东西,但我无法确定它。

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

int main(void)
{
    char returnData[2048]; 
    struct dirent *de;  // Pointer for directory entry

    // opendir() returns a pointer of DIR type. 
    DIR *dr = opendir(".");

    if (dr == NULL)  // opendir returns NULL if couldn't open directory
    {
        printf("Could not open current directory" );
        return 0;
    }

    // Refer http://pubs.opengroup.org/onlinepubs/7990989775/xsh/readdir.html
    // for readdir()
    while ((de = readdir(dr)) != NULL)
            printf("%s\n", de->d_name);  //strcat(returnData, de->d_name); produces segmentation fault here.

    closedir(dr);    
    return 0;
}

【问题讨论】:

  • 当您连接的字符数超过 2048 时,它将表现出未定义的行为。在此之前它应该可以正常工作。所以结果取决于当前工作目录的文件名中的字符数。文件越多,名称越长,它就会越早表现出未定义的不良行为。空终止符与名称中的字符一起写入。名称为 a 的文件会导致 strcat 写入两个字节:一个用于 a,而 0 字节用于字符串的空终止符。
  • 谢谢,杰夫,我应该提到我正在测试一个文件名中的字符远少于 2048 个字符的目录。作为进一步的测试,我使我的数组大得离谱,但仍然收到 seg 错误。
  • 您的第一个 strcat 会导致未定义的行为,因为 returnData[] 没有像您假设的那样初始化为 0。解决方法很明显。
  • 好吧,事实证明这不是问题,但它把我带到了那里。由于我遇到的编译错误,我删除了我的 memset……因为我是个白痴,并将我的 char 数组作为全局变量。现在它已修复并恢复原状,事实证明我没有注释掉添加 strcat 行时的打印行......这让我直接陷入了糟糕的一天:)

标签: c


【解决方案1】:

第一个变化:

 char returnData[2048]; 

到

 char returnData[2048] = { '\0' };

正如 cmets 中已经提到的,您应该使用 Zeros/NUL-Terminator 初始化您的数组,因此对 strcat 的调用定义为 strcat 将'\0' 替换为src 参数。

正如一些编译器抱怨使用strncat 或类似的而不是strcat。 另外不要忘记,您还需要附加 '\n' 以获得与 printf 相同的输出。

您可以预先计算长度导致两个循环 或动态调整缓冲区大小。

顺便说一句:为什么要将其存储在单个字符串中?

【讨论】:

  • 使用strncat() 比使用strcat() 问题多得多。你看到char minibuf[16] = ""; strncat(minibuf, "ABCDEFGHIJKLMNOP", sizeof(minibuf)); 的问题了吗?如果没有,你已经证明了我的观点。
  • "正如 cmets 中已经提到的,您应该使用 Zeros/NUL-Terminator 初始化您的数组,因此对 strcat 的调用被定义为 strcat 用 src 参数替换了 '\0'。"并非总是如此,事实上我会强烈反对,只是 `char returnData[2048] = ""`
  • 感谢 Inrin,我的修复在上面的 cmets 中进行了解释。为了回答你的问题,我正在做一个创建简单文件传输客户端和服务器的类项目。我需要向客户端应用程序发送一个目录列表,所以我只是将它吐成一个我在另一端解析的字符串。可能有更好的方法......但这似乎又快又容易。
  • @JonathanLeffler 是的,我看到了问题,对于strncat,您必须跟踪已使用的字节。 strlcat 例如明确地获取缓冲区大小。所以我写了«或类似»。 @Stargateur The strcat function appends a copy of the string pointed to by s2 (including the terminating null character) to the end of the string pointed to by s1. The initial character of s2 overwrites the null character at the end of s1. (ISO/IEC 9899:TC2, 7.21.3.1) 你能解释一下吗?最后 "" 无论如何都会评估为 {'\0'} 。 (参见 6.7.8。示例 8)。
【解决方案2】:

你错过了几件事。首先不要使用幻数...2048 来自哪里? (舔手指,举起它说“是的,这应该够好了”?)limits.h 标头提供了宏PATH_MAX,它保证为所有文件系统条目提供足够的存储空间——改用它,例如:

#include <limits.h>     /* for PATH_MAX */
...
    char returnData[PATH_MAX] = "";  /* initialize to all zero is good habit */

(接下来,我确定是一个错字,但null 不是NULL)

如果您只是想将de-&gt;d_name 复制到returnData,则使用将de-&gt;d_name 复制到returnData 的函数,例如strcpy,例如

    while ((de = readdir(dr)) != NULL) {
        strcpy (returnData, de->d_name);
        puts (returnData); 
    }

(当然,它在每次迭代时都会被覆盖,因此您不会使用returnData 返回文件列表,尽管returnData 是在当前函数中使用automatic-storage 声明的 如果在另一个函数中声明,则无法返回以开头...)

因此,将de-&gt;d_name 复制到returnData 的所有这些拐弯抹角的做法让您完全离开了起点,只能一次输出一个条目的名称。

实际为每个目录条目分配存储空间

我怀疑您真正想要做的是将目录中的所有文件读入存储,您可以从函数中返回名称列表,以便在代码中进行进一步处理。这是常见的做法,但不能用单个字符数组来做。

相反,您需要声明一个 pointer-to-pointer-to-char(例如“双指针”,char **dlist;),这将允许您分配一些初始数量的指针(比如8)然后realloc 更多指针,以容纳任何目录中的所有文件或目录名称。然后,您只分配每个名称所需的存储空间(nul-terminating 字符为 +1),并将每个名称的存储空间分配给其相应的指针,并将名称复制到您分配的新存储空间。

这样你就可以从任何你喜欢的地方返回一个指向你的名字集合的指针。 (记住对象将 allocated-storage 类型的生命周期一直持续到内存被释放或程序结束)类似:

#define NPTRS  8        /* initial number of pointers to allocate */
...
    char **dlist = NULL,                    /* ptr-to-ptr-to-char for names */
    size_t idx = 0,                         /* current index                */
        nptrs = NPTRS;                      /* number of pointers allocated */
    ...
    /* allocate/validate nptrs pointers  */
    if ((dlist = calloc (nptrs, sizeof *dlist)) == NULL) {
        perror ("calloc-nptrs");
        return EXIT_FAILURE;
    }

    while ((de = readdir (dp))) {
    ...
        /* check if dlist pointer limit reached - realloc */
        if (idx == nptrs) { /* alwasy realloc to temporary pointer */
            void *tmp = realloc (dlist, nptrs * 2 * sizeof *dlist);
            if (!tmp) {     /* validate reallocation */
                perror ("realloc-dlist");
                break;  /* break, don't exit, original storage still valid */
            }
            dlist = tmp;    /* assign reallocated block to dlist */
            /* (optional) set all newly allocated memory to zero */
            memset (dlist + nptrs, 0, nptrs * sizeof *dlist);
            nptrs *= 2;     /* update the number of allocated pointers */
        }

        /* allocate storage for name in dlist */
        if ((dlist[idx] = malloc (strlen (de->d_name) + 1)) == NULL) {
            char errbuf[PATH_MAX] = "";     /* storage for perror message */
            sprintf (errbuf, "malloc failed '%s'", de->d_name);
            perror (errbuf);
            break;
        }
        strcpy (dlist[idx++], de->d_name);  /* copy to new storage at idx */
    }

现在您已将所有名称存储在dlist 中,其中idx 表示存储的名称数量。您可以从任何函数返回dlist(您还希望通过参数返回idx,以便存储的文件数也可以在调用函数中返回,或者将重新分配移动到您的副本下方(并包括'可选'memset) 以确保在最后一个有效条目之后始终有一个标记NULL 指针——这提供了另一种指示返回的有效名称的方法。

正如您已经(或将要)发现的那样,readdir 不会以任何特定顺序读取目录条目。为了对输出有用,使用qsort 排序是对您存储的文件名进行排序的最简单方法。下面的示例显示了一个简单的升序排序。

总而言之,您可以读取任何目录中的条目(默认情况下作为第一个参数传递给程序或来自'.'(当前目录))。代码将根据需要分配指针并重新分配。该代码为每个条目准确分配strlen(de-&gt;d_name) + 1 存储字符,将新的内存块分配给dlist[idx],然后将条目复制到dlist[idx](如果您的库,您可以使用dlist[idx] = strdup (de-&gt;d_name); 一步来分配和复制提供strdup -- 但请记住strdup 正在分配内存,因此您应该在继续之前验证成功。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>  /* opendir  */
#include <dirent.h>     /* opendir, readdir */
#include <limits.h>     /* for PATH_MAX */

#define NPTRS  8        /* initial number of pointers to allocate */

/** qsort string comparison (sort ascending) */
int cmpstr (const void *a, const void *b)
{
    return strcmp (*(char * const *) a, *(char * const *) b);
}

int main (int argc, char **argv) {

    char **dlist = NULL,                    /* ptr-to-ptr-to-char for names */
        *dname = argc > 1 ? argv[1] : ".";  /* dirname supplied (. default) */
    size_t idx = 0,                         /* current index                */
        nptrs = NPTRS;                      /* number of pointers allocated */
    struct dirent *de = NULL;               /* dirent pointer (readdir)     */
    DIR *dp = opendir (dname);              /* directory pointer (opendir)  */

    if (!dp) {  /* validate directory open for reading */
        char errbuf[PATH_MAX] = "";         /* storage for perror message */
        sprintf (errbuf, "opendir failed on '%s'", dname);
        perror (errbuf);
        return EXIT_FAILURE;
    }

    /* allocate/validate nptrs pointers  */
    if ((dlist = calloc (nptrs, sizeof *dlist)) == NULL) {
        perror ("calloc-nptrs");
        return EXIT_FAILURE;
    }

    while ((de = readdir (dp))) {
        /* skip dot files */
        if (!strcmp (de->d_name, ".") || !strcmp (de->d_name, ".."))
            continue;

        /* check if dlist pointer limit reached - realloc */
        if (idx == nptrs) { /* alwasy realloc to temporary pointer */
            void *tmp = realloc (dlist, nptrs * 2 * sizeof *dlist);
            if (!tmp) {     /* validate reallocation */
                perror ("realloc-dlist");
                break;  /* break, don't exit, original storage still valid */
            }
            dlist = tmp;    /* assign reallocated block to dlist */
            /* (optional) set all newly allocated memory to zero */
            memset (dlist + nptrs, 0, nptrs * sizeof *dlist);
            nptrs *= 2;     /* update the number of allocated pointers */
        }

        /* allocate storage for name in dlist */
        if ((dlist[idx] = malloc (strlen (de->d_name) + 1)) == NULL) {
            char errbuf[PATH_MAX] = "";     /* storage for perror message */
            sprintf (errbuf, "malloc failed '%s'", de->d_name);
            perror (errbuf);
            break;
        }
        strcpy (dlist[idx++], de->d_name);  /* copy to new storage at idx */
    }
    closedir (dp);          /* close directory */

    /* qsort names stored in dlist */
    qsort (dlist, idx, sizeof *dlist, cmpstr);

    /* output all file/directory names stored, freeing memory as you go */
    printf ("'%s' contains '%zu' files:\n\n", dname, idx);
    for (size_t i = 0; i < idx; i++) {
        puts (dlist[i]);    /* output name */
        free (dlist[i]);    /* free storage for name */
    }
    free (dlist);           /* free pointers */

    return 0;
}

使用/输出示例

$ ./bin/opendir_readdir_dyn_char_basic .
'.' contains '1860' files:

3darrayaddr.c
3darrayalloc.c
3darrayfill.c
BoggleData.txt
DoubleLinkedList-old.c
DoubleLinkedList.c
DoubleLinkedList.diff
InputFile.txt
MonoSound.wav
...
xsplit.sh
xstrncpy.c
zeronotzero.c

内存使用/错误检查

另请注意,在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1) 始终保留指向起始地址的指针 对于内存块,(2) 当不再需要它时可以释放。

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

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

$ valgrind ./bin/opendir_readdir_dyn_char_basic .
==16528== Memcheck, a memory error detector
==16528== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==16528== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==16528== Command: ./bin/opendir_readdir_dyn_char_basic .
==16528==
'.' contains '1860' files:

3darrayaddr.c
3darrayalloc.c
3darrayfill.c
BoggleData.txt
DoubleLinkedList-old.c
DoubleLinkedList.c
DoubleLinkedList.diff
InputFile.txt
MonoSound.wav
...
xsplit.sh
xstrncpy.c
zeronotzero.c
==16528==
==16528== HEAP SUMMARY:
==16528==     in use at exit: 0 bytes in 0 blocks
==16528==   total heap usage: 1,872 allocs, 1,872 frees, 109,843 bytes allocated
==16528==
==16528== All heap blocks were freed -- no leaks are possible
==16528==
==16528== For counts of detected and suppressed errors, rerun with: -v
==16528== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多