我试图从 c 中的文件中读取一行并动态分配内存,但结果总是不好

I'm trying to read a line from a file in c and dynamically allocate memory but the result is always bad

char * readline(FILE *fp, char *buffer) {
  char ch;
  int i = 0, buff_len = 0;

  buffer = malloc(buff_len);

  while ((ch = fgetc(fp)) != '\n' && ch != EOF) {
    ++buff_len;
    buffer = realloc(buffer, buff_len);

    buffer[i] = ch;

    ++i;
  }

  return buffer;
}

我正在尝试读取一个字符后直接分配内存。

代码从不通过附加空字符使分配的缓冲区成为字符串,也不会为其分配 space。

char * readline(FILE *fp, char *buffer) {
  int ch;
  int i = 0;
  size_t buff_len = 0;

  buffer = malloc(buff_len + 1);
  if (!buffer) return NULL;  // Out of memory

  while ((ch = fgetc(fp)) != '\n' && ch != EOF) {
    buff_len++;
    void *tmp = realloc(buffer, buff_len + 1);
    if (tmp == NULL) {
      free(buffer);
      return NULL; // Out of memory
    }
    buffer = tmp;

    buffer[i] = (char) ch;
    i++;
  }
  buffer[i] = '[=10=]';

  // Detect end
  if (ch == EOF && (i == 0 || ferror(fp))) {
    free(buffer);
    return NULL;
  }
  return buffer;
}

注:
在每次迭代中重新分配有点浪费。
buffer 的输入值从未使用过。也许重新设计OP的功能。
ch 应该是 int 以正确区分 char fromEOF`。

如 chux 所述,建议的替代方案。显示文本文件的示例代码。

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

char * readline(FILE *fp) {
    char * buffer = malloc(1024);   /* assume longest line < 1023 chars */
    int ch;
    int i = 0;
    while ((ch = fgetc(fp)) != '\n' && ch != EOF)
        buffer[i++] = ch;
    if(ch == EOF){                  /* if eof, free buffer, return */
        free(buffer);
        return 0;
    }
    buffer[i++] = 0;                /* add 0 terminator */
    buffer = realloc(buffer, i);
    return buffer;
}

int main(int argc, char *argv[])
{
FILE *fp;
char *pline;
    if(argc < 2)
        return 0;
    fp = fopen(argv[1], "r");
    while(1){
        pline = readline(fp);
        if(pline == 0)
            break;
        printf("%s\n", pline);
        free(pline);
    }
    fclose(fp);
    return 0;
}

将指针传递给函数以进行分配、填充和 return 有几个微妙之处。最重要的是要理解,当将指针传递给函数时,函数会收到该指针的 copy 以及一个新的单独地址。如果随后在函数中为指针分配 space,则 必须 return 指向调用者的指针地址(此处为 main())或调用者将无法访问您存储在新分配的内存块中的值。

要克服这个问题,并能够传递一个指向函数的指针进行分配和填充, 不必使用 return,您必须通过函数指针的地址,即:

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

否则,正如评论和上面所讨论的,没有理由将指向缓冲区的指针传递给函数。您可以简单地声明 buffer 局部函数,为其动态分配 space,并将 return 起始地址分配给新的内存块。

无论哪种方式都没有错,它实际上归结为您需要什么,但您想要清楚传递给函数的内容以及原因。下面是一个简短的示例,将指针的地址传递给 readline 并允许函数在每一行中 allocate/fill 而无需使用 return。现在,在这种情况下,return 指向您的行的指针永远不会受到伤害。它提供了一种确定 success/failure 的方法,并且可以根据需要灵活地分配 return:

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

#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 更有意义。如果您关心的只是分配恰好容纳字符串所需的数量,则按照描述进行分配,并在末尾为 strlen(buf) + 1 个字符执行一个最终的 realloc

此外,以这种方式分配,您可以随时设置 #define NCHAR 1 并强制开始分配,如果您愿意,还可以设置 1-char

内存Leak/Error检查

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 个责任:(1) 始终保留指向内存块起始地址的指针,因此,(2) 它可以是不再需要时释放。您必须使用内存错误检查程序来确保您没有写入 beyond/outside 您分配的内存块并确认您已释放所有分配的内存。对于 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 并修复 returning [= 时潜在的内存泄漏29=] 来自 readline EOF