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

I';我试图从c语言中的文件中读取一行并动态分配内存,但结果总是不好,c,file,memory,dynamic,allocation,C,File,Memory,Dynamic,Allocation,我试图在读取一个字符后直接分配内存。代码从不通过添加空字符使分配的缓冲区成为字符串,也不为其分配空间 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 = r

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

代码从不通过添加空字符使分配的缓冲区成为字符串,也不为其分配空间

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;
}
注:
在每次迭代中重新分配有点浪费。
从不使用
缓冲区的输入值。也许重新设计OP的功能。

ch
应为
int
,以正确区分
char和
EOF`。

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

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] = '\0';

  // Detect end
  if (ch == EOF && (i == 0 || ferror(fp))) {
    free(buffer);
    return NULL;
  }
  return buffer;
}
#包括
#包括
char*readline(文件*fp){
char*buffer=malloc(1024);/*假设最长行小于1023个字符*/
int-ch;
int i=0;
而((ch=fgetc(fp))!='\n'&&ch!=EOF)
缓冲区[i++]=ch;
if(ch==EOF){/*if-EOF,空闲缓冲区,返回*/
自由(缓冲);
返回0;
}
缓冲区[i++]=0;/*添加0终止符*/
缓冲区=realloc(缓冲区,i);
返回缓冲区;
}
int main(int argc,char*argv[])
{
文件*fp;
字符线;
如果(argc<2)
返回0;
fp=fopen(argv[1],“r”);
而(1){
pline=读线(fp);
如果(pline==0)
打破
printf(“%s\n”,pline);
免费(pline);
}
fclose(fp);
返回0;
}

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

为了克服此问题,并且能够传递指向函数的指针以进行分配和填充,而不必使用返回,您必须传递指向函数的指针地址,即:

#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;
}
否则,正如上面的注释所讨论的,没有理由将指向缓冲区的指针传递给函数。您只需将
buffer
声明为函数的本地,为其动态分配空间,并将起始地址返回到新的内存块

无论采用哪种方式,都没有什么错,它实际上可以归结为您需要什么,但您希望清楚地了解您传递给函数的内容以及原因。下面是一个简短的示例,它将指针的地址传递给
readline
,并允许函数分配/填充每一行,而无需使用返回。现在,在这种情况下,返回一个指向行的指针是不会有任何伤害的。它提供了一种确定成功/失败的方法,并提供了分配回报的灵活性(如果您愿意):

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

#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.
注意

您确实不希望为每个字符分配<代码>malloc
是一种相对昂贵的操作。为每行分配一些合理预期的字符数,然后在达到该限制时分配
realloc
,比为每个字符分配
realloc
更有意义。如果您只关心准确分配保存字符串所需的数量,则按照说明进行分配,并在末尾为
strlen(buf)+1个
字符执行最后一次
realloc

此外,通过这种方式分配,您可以始终设置
#定义NCHAR 1
,并强制开始分配和
1-char
(如果愿意)

内存泄漏/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1)始终保留指向内存块起始地址的指针,以便(2)不再需要时可以释放它。您必须使用内存错误检查程序,以确保您没有在分配的内存块之外写入,并确认您已释放了分配的所有内存。对于Linux,
valgrind
是正常的选择。有很多微妙的方法可以误用内存块,从而导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们都很容易使用。只需运行程序即可

$ ./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.
您应该看到
所有堆块都已释放--不可能发生泄漏
错误摘要:每次都有0个上下文中的0个错误


注意:更新以反映对
ch
的返回和键入的注释,并修复在
EOF
上从
readline
返回
NULL
时可能出现的内存泄漏问题
buffer
作为参数传递并立即覆盖它的原因是什么?为什么不把它变成一个局部变量呢?正确格式化代码。(文本下有一个
edit
链接)。为什么不至少分配
nchar=64
用于
buffer
并保留一个charcount,当
charcount+1==nchar
时,则
realloc(buffer,nchar*2);nchar*=2?好的,这绝对是我不希望我的CPU运行的代码类型…我也希望如此,但我从测试的角度考虑它,并解释一个空字符串的测试或简单的
NULL
。它值得一张纸条。@chux-比纸条更值得-编辑和更改。但是当
buflen==0
时,不管怎样,
line
main
中被释放,都没有内存泄漏。谢谢。非常感谢。关于
if(ch==EOF){…*buffer=NULL
的提示。当文件中的最后一行不是以
'\n'
结尾时,有多种方法。此代码返回
NULL
并丢失最后的字符。类似
fgets()
的功能
$ 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)