当前位置:  开发笔记 > 编程语言 > 正文

我正在尝试从c中的文件读取一行并动态分配内存,但结果总是很糟糕

如何解决《我正在尝试从c中的文件读取一行并动态分配内存,但结果总是很糟糕》经验,为你挑选了1个好方法。

将指针传递给函数进行分配,填充和返回时,存在一些微妙之处。要理解的最重要的一点是,当将指针传递给函数时,函数会接收到具有新的单独地址的该指针的副本。如果您随后在函数中为指针分配了空间,则必须将指针的地址返回给调用方(main()此处),否则调用方将无法访问您新分配的内存块中存储的值。

为了克服此问题,并且能够在利用返回值的情况下将指针传递给函数进行分配和填充,必须将指针的地址传递给函数,即:

char *readline (FILE *fp, char **buffer) 

否则,如注释和以上所讨论,没有理由将指针传递给缓冲区到函数。您只需声明buffer该函数的局部变量,为该函数动态分配空间,然后将起始地址返回到新的内存块即可。

无论哪种方式都没有错,它确实可以归结为您所需要的,但是您想要弄清楚要传递给函数的内容以及原因。下面是一个简短的示例,将指针的地址传递到该地址,readline并允许该函数分配/填充每一行而无需利用返回值。现在,在这种情况下,将指针返回到您的行将毫不费力。它提供了一种确定成功/失败的方法,以及根据需要灵活分配收益的方法:

#include 
#include 

#define NCHAR 64

char *readline (FILE *fp, char **buffer);

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

    char *line = NULL;
    size_t idx = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    if (!fp) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (readline (fp, &line)) {  /* read each line in 'fp' */
        printf (" line[%2zu] : %s\n", idx++, line);
        free (line);
        line = NULL;
    }
    if (fp != stdin) fclose (fp);

    return  0;
}

/* read line from 'fp' allocate *buffer NCHAR in size
 * realloc as necessary. Returns a pointer to *buffer
 * on success, NULL otherwise.
 */
char *readline (FILE *fp, char **buffer) 
{
    int ch;
    size_t buflen = 0, nchar = NCHAR;

    *buffer = malloc (nchar);    /* allocate buffer nchar in length */
    if (!*buffer) {
        fprintf (stderr, "readline() error: virtual memory exhausted.\n");
        return NULL;
    }

    while ((ch = fgetc(fp)) != '\n' && ch != EOF) 
    {
        (*buffer)[buflen++] = ch;

        if (buflen + 1 >= nchar) {  /* realloc */
            char *tmp = realloc (*buffer, nchar * 2);
            if (!tmp) {
                fprintf (stderr, "error: realloc failed, "
                                "returning partial buffer.\n");
                (*buffer)[buflen] = 0;
                return *buffer;
            }
            *buffer = tmp;
            nchar *= 2;
        }
    }
    (*buffer)[buflen] = 0;           /* nul-terminate */

    if (buflen == 0 && ch == EOF) {  /* return NULL if nothing read */
        free (*buffer);
        *buffer = NULL;
    }

    return *buffer;
}

输入项

$ cat ../dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

输出量

$ ./bin/readline ../dat/captnjack.txt
 line[ 0] : This is a tale
 line[ 1] : Of Captain Jack Sparrow
 line[ 2] : A Pirate So Brave
 line[ 3] : On the Seven Seas.

注意

您确实不想分配每个字符。malloc是一个相对昂贵的操作。为每行分配一些合理预期的字符数,然后realloc达到此限制比realloc为每个字符分配的字符数有意义得多。如果只考虑分配字符串所需的确切数量,请按照说明进行分配,最后reallocstrlen(buf) + 1字符进行最后一个处理。

此外,以这种方式进行分配,您始终可以根据需要设置#define NCHAR 1并强制开始分配1-char

内存泄漏/错误检查

在您的任何动态分配内存的代码中,您对分配的任何内存块都有2种责任:(1)始终保留指向内存块起始地址的指针,因此,(2)在没有内存块的情况下可以将其释放需要更长的时间。必须使用一个内存错误检查程序来确保您没有在分配的内存块之外/之外进行写操作,并确认您已释放所有分配的内存。对于Linux valgrind是正常的选择。滥用内存块的微妙方法有很多,它们可能导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们都很容易使用。只需通过它运行程序即可。

$ valgrind ./bin/readline ../dat/captnjack.txt
==16460== Memcheck, a memory error detector
==16460== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==16460== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==16460== Command: ./bin/readline ../dat/captnjack.txt
==16460==
 line[ 0] : This is a tale
 line[ 1] : Of Captain Jack Sparrow
 line[ 2] : A Pirate So Brave
 line[ 3] : On the Seven Seas.
==16460==
==16460== HEAP SUMMARY:
==16460==     in use at exit: 0 bytes in 0 blocks
==16460==   total heap usage: 6 allocs, 6 frees, 888 bytes allocated
==16460==
==16460== All heap blocks were freed -- no leaks are possible
==16460==
==16460== For counts of detected and suppressed errors, rerun with: -v
==16460== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

您应该看到All heap blocks were freed -- no leaks are possibleERROR SUMMARY: 0 errors from 0 contexts每一次。

注意:已更新,以反映对return和类型的注释,ch并修复NULLreadlineon 返回时潜在的内存泄漏EOF



1> David Rankin..:

将指针传递给函数进行分配,填充和返回时,存在一些微妙之处。要理解的最重要的一点是,当将指针传递给函数时,函数会接收到具有新的单独地址的该指针的副本。如果您随后在函数中为指针分配了空间,则必须将指针的地址返回给调用方(main()此处),否则调用方将无法访问您新分配的内存块中存储的值。

为了克服此问题,并且能够在利用返回值的情况下将指针传递给函数进行分配和填充,必须将指针的地址传递给函数,即:

char *readline (FILE *fp, char **buffer) 

否则,如注释和以上所讨论,没有理由将指针传递给缓冲区到函数。您只需声明buffer该函数的局部变量,为该函数动态分配空间,然后将起始地址返回到新的内存块即可。

无论哪种方式都没有错,它确实可以归结为您所需要的,但是您想要弄清楚要传递给函数的内容以及原因。下面是一个简短的示例,将指针的地址传递到该地址,readline并允许该函数分配/填充每一行而无需利用返回值。现在,在这种情况下,将指针返回到您的行将毫不费力。它提供了一种确定成功/失败的方法,以及根据需要灵活分配收益的方法:

#include 
#include 

#define NCHAR 64

char *readline (FILE *fp, char **buffer);

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

    char *line = NULL;
    size_t idx = 0;
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    if (!fp) {
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    while (readline (fp, &line)) {  /* read each line in 'fp' */
        printf (" line[%2zu] : %s\n", idx++, line);
        free (line);
        line = NULL;
    }
    if (fp != stdin) fclose (fp);

    return  0;
}

/* read line from 'fp' allocate *buffer NCHAR in size
 * realloc as necessary. Returns a pointer to *buffer
 * on success, NULL otherwise.
 */
char *readline (FILE *fp, char **buffer) 
{
    int ch;
    size_t buflen = 0, nchar = NCHAR;

    *buffer = malloc (nchar);    /* allocate buffer nchar in length */
    if (!*buffer) {
        fprintf (stderr, "readline() error: virtual memory exhausted.\n");
        return NULL;
    }

    while ((ch = fgetc(fp)) != '\n' && ch != EOF) 
    {
        (*buffer)[buflen++] = ch;

        if (buflen + 1 >= nchar) {  /* realloc */
            char *tmp = realloc (*buffer, nchar * 2);
            if (!tmp) {
                fprintf (stderr, "error: realloc failed, "
                                "returning partial buffer.\n");
                (*buffer)[buflen] = 0;
                return *buffer;
            }
            *buffer = tmp;
            nchar *= 2;
        }
    }
    (*buffer)[buflen] = 0;           /* nul-terminate */

    if (buflen == 0 && ch == EOF) {  /* return NULL if nothing read */
        free (*buffer);
        *buffer = NULL;
    }

    return *buffer;
}

输入项

$ cat ../dat/captnjack.txt
This is a tale
Of Captain Jack Sparrow
A Pirate So Brave
On the Seven Seas.

输出量

$ ./bin/readline ../dat/captnjack.txt
 line[ 0] : This is a tale
 line[ 1] : Of Captain Jack Sparrow
 line[ 2] : A Pirate So Brave
 line[ 3] : On the Seven Seas.

注意

您确实不想分配每个字符。malloc是一个相对昂贵的操作。为每行分配一些合理预期的字符数,然后realloc达到此限制比realloc为每个字符分配的字符数有意义得多。如果只考虑分配字符串所需的确切数量,请按照说明进行分配,最后reallocstrlen(buf) + 1字符进行最后一个处理。

此外,以这种方式进行分配,您始终可以根据需要设置#define NCHAR 1并强制开始分配1-char

内存泄漏/错误检查

在您的任何动态分配内存的代码中,您对分配的任何内存块都有2种责任:(1)始终保留指向内存块起始地址的指针,因此,(2)在没有内存块的情况下可以将其释放需要更长的时间。必须使用一个内存错误检查程序来确保您没有在分配的内存块之外/之外进行写操作,并确认您已释放所有分配的内存。对于Linux valgrind是正常的选择。滥用内存块的微妙方法有很多,它们可能导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们都很容易使用。只需通过它运行程序即可。

$ valgrind ./bin/readline ../dat/captnjack.txt
==16460== Memcheck, a memory error detector
==16460== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==16460== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==16460== Command: ./bin/readline ../dat/captnjack.txt
==16460==
 line[ 0] : This is a tale
 line[ 1] : Of Captain Jack Sparrow
 line[ 2] : A Pirate So Brave
 line[ 3] : On the Seven Seas.
==16460==
==16460== HEAP SUMMARY:
==16460==     in use at exit: 0 bytes in 0 blocks
==16460==   total heap usage: 6 allocs, 6 frees, 888 bytes allocated
==16460==
==16460== All heap blocks were freed -- no leaks are possible
==16460==
==16460== For counts of detected and suppressed errors, rerun with: -v
==16460== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

您应该看到All heap blocks were freed -- no leaks are possibleERROR SUMMARY: 0 errors from 0 contexts每一次。

注意:已更新,以反映对return和类型的注释,ch并修复NULLreadlineon 返回时潜在的内存泄漏EOF

推荐阅读
U友50081205_653
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有