【问题标题】:Reading an unknown length line from stdin in c with fgets使用 fgets 从 c 中的标准输入读取未知长度的行
【发布时间】:2015-04-11 10:26:31
【问题描述】:

我正在尝试使用 C 语言从标准输入读取未知长度的行。

我在网上看到这个:

char** str;
gets(&str);

但这似乎给我带来了一些问题,我真的不明白怎么可能这样做。

你能解释一下为什么这个例子有效/无效 以及实现它的正确方法是什么(使用malloc?)

【问题讨论】:

  • 系统函数 'gets()' 有很多问题,包括超出可用输入缓冲区的可能性。建议使用 fgets(),它允许在输入字符串上设置最大长度。
  • 建议使用 getline() 函数。如果您的系统上还没有该功能,可以从互联网上下载源代码。

标签: c


【解决方案1】:

您不想要指向char 的指针,请使用chars 的数组

char str[128];

或指向char的指针

char *str;

如果你选择一个指针,你需要使用malloc预留空间

str = malloc(128);

那么你可以使用fgets

fgets(str, 128, stdin);

并删除尾随的换行符

char *ptr = strchr(str, '\n');
if (ptr != NULL) *ptr = '\0';

要读取任意长行,您可以使用getline(添加到 GNU 版本的 libc 的函数):

#define _GNU_SOURCE
#include <stdio.h>

char *foo(FILE * f)
{
    int n = 0, result;
    char *buf;

    result = getline(&buf, &n, f);
    if (result < 0) return NULL;
    return buf;
}

或您自己使用fgetsrealloc 实现:

char *getline(FILE * f)
{
    size_t size = 0;
    size_t len  = 0;
    size_t last = 0;
    char *buf = NULL;

    do {
        size += BUFSIZ; /* BUFSIZ is defined as "the optimal read size for this platform" */
        buf = realloc(buf, size); /* realloc(NULL,n) is the same as malloc(n) */            
        /* Actually do the read. Note that fgets puts a terminal '\0' on the
           end of the string, so we make sure we overwrite this */
        if (buf == NULL) return NULL;
        fgets(buf + last, BUFSIZ, f);
        len = strlen(buf);
        last = len - 1;
    } while (!feof(f) && buf[last] != '\n');
    return buf;
}

调用它使用

char *str = getline(stdin);

if (str == NULL) {
    perror("getline");
    exit(EXIT_FAILURE);
}
...
free(str);

More info

【讨论】:

  • 这不能处理用户在一行中输入超过 128 个字符的情况。因此,它没有解决读取任意长度行的问题。
  • @Peter:当然可以。
  • @alk,在彼得的评论之后有一个编辑,彼得,谢谢你指出这一点。
  • @Alter Mann - realloc() 可能会失败。您的代码现在更能够处理任意长度,但假设 realloc() 总是成功。除了那个小问题,现在好多了。
  • 我相信“fgets(buf + last, size, f);”应该是“fgets(buf + last, BUFSIZ, f);”否则“realloc”可能会失败。
【解决方案2】:

首先,gets() 无法防止缓冲区溢出。这使得它变得如此危险,它已从最新的 C 标准中删除。它不应该被使用。但是,通常的用法类似于

char buffer[20];
gets(buffer);      /*  pray that user enters no more than 19 characters in a line */

您的用法是将gets() 传递给指向char 指针的指针。这不是gets() 所期望的,因此您的代码甚至无法编译。

评论中反映的祈祷元素是gets()如此危险的原因。如果用户输入 20 个(或更多)字符,gets() 将愉快地在buffer 末尾写入数据。程序员无法在代码中阻止这种情况(没有访问硬件来电死输入过多数据的用户,这超出了标准 C 的范围)。

但是,要回答您的问题,唯一的方法是分配一定大小的缓冲区,以某种受控方式读取数据直到达到该大小,在需要时重新分配以获得更大的大小,并继续直到换行(或结束-of-file 或其他输入错误条件)。

malloc() 可用于初始分配。 malloc()realloc() 可用于重新分配(如果需要)。请记住,当不再需要数据时,必须释放以这种方式分配的缓冲区(使用free()) - 否则会导致内存泄漏。

【讨论】:

  • 你真的应该使用 calloc 来动态分配数组
  • 没有必要使用 calloc()。就像不需要将变量设置为零一样,如果后续的第一个操作是将该变量设置为另一个值。
【解决方案3】:

使用 getline() 函数,这将返回行的长度,以及在分配的内存区域中指向行内容的指针。 (确保在完成后将行指针传递给 free())

【讨论】:

    【解决方案4】:

    “使用 fgets 从 c 中的标准输入读取未知长度的行”

    延迟响应 - Windows 方法:

    OP 没有指定 Linux 或 Windows,但针对这个问题发布的可行答案似乎都有共同的 getline() 函数,即POSIX onlygetline()popen() 等函数非常有用且功能强大,但遗憾的是不包含在 Windows 环境中。

    因此,在 Windows 环境中实现这样的任务需要不同的方法。链接here 描述了一种可以从stdin 读取输入的方法,并且已经在开发它的系统上进行了高达 1.8 GB 的测试。 (在链接中也有描述。)_下面的简单代码sn-p是用下面的命令行测试的,可以在stdin上大量读取:

    cd c:\dev && dir /s  // approximately 1.8Mbyte buffer is returned on my system 
    

    简单示例:

    #include "cmd_rsp.h"
    int main(void)
    {
        char *buf = {0};
        buf = calloc(100, 1);//initialize buffer to some small value
        if(!buf)return 0;
        cmd_rsp("dir /s", &buf, 100);//recursive directory search on Windows system
        printf("%s", buf);
        free(buf);
        
        return 0;
    }
    

    cmd_rsp() 在上面的链接中有完整的描述,但它本质上是一个 Windows 实现,包括 popen()getline() 类似的功能,打包到这个非常简单的函数中。

    【讨论】:

      【解决方案5】:

      如果您想输入未知长度的字符串或输入,请尝试使用以下代码。

      #include <stdio.h>
      #include <conio.h>
      #include <stdlib.h>
      
      
      int main()
      {
      char  *m;
      clrscr();
      printf("please input a string\n");
      scanf("%ms",&m);
      if (m == NULL)
          fprintf(stderr, "That string was too long!\n");
      else
      {
          printf("this is the string %s\n",m);
          /* ... any other use of m */
          free(m);
      
      
      }
      
      getch();
      return 0;
      
      }
      

      注意 %ms, %as 是 GNU 扩展..

      【讨论】:

      • 除非正在读取的字符串(包括添加的终止符)不大于 char*(字符指针)的大小,否则此代码完全错误。更长的时间,这是调用未定义行为的秘诀。进一步free(m) 被传递一个不确定的地址。你确定你没有想到getline() 吗?
      • 这会调用未定义的行为,因为 'm' 没有特别指向任何东西。所以可以读入内存中的任何地方。这是引发段错误事件的一种非常好的方法。
      • conio.h 不可移植。 “%ms”不可移植
      猜你喜欢
      • 1970-01-01
      • 2015-02-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多