【问题标题】:Is there a good alternative to fgets? [closed]fgets有什么好的替代品吗? [关闭]
【发布时间】:2016-01-09 15:36:15
【问题描述】:

我只是一名年轻的计算机科学专业的学生,​​目前我对从 stdin 读取字符串的最佳做法有点困惑。我知道有很多方法可以做到这一点,有些比其他更安全,等等...... 我目前需要一个防止缓冲区溢出并将空终止符 (\0) 附加到字符串末尾的函数。我发现 fgets 对此非常有用,但是......它会停止使用 \n 或 EOF 读取!如果我希望用户一次输入多行怎么办?是否有其他功能可以帮助我做到这一点? 如果这个问题对你们中的一些人来说似乎很愚蠢,我很抱歉,但请理解我! 任何帮助将不胜感激。

【问题讨论】:

  • Similar question 使用getline
  • 输入总是需要在 EOF 结束,即用户在 Windows 上键入 CTRL + Z 或在 Linux 上键入 CTRL + D。
  • 这个similar question 有一个解释如何使用fgetc 的答案
  • 我知道为什么fgets 停在\n 可能是个问题(尽管老实说,我确实看到为什么它是一个问题 - 你可以再次运行它来获取下一行!)但是当它遇到EOF时你想象它会做什么?
  • @arainone 我知道 fgetc、getc 和 getchar(void),它们的功能非常相似。我正在寻找的是一个类似于 fgets 的函数,因为它更安全(它不允许缓冲区溢出)并且它附加了一个空终止符。

标签: c string fgets


【解决方案1】:
#define INITALLOC  16  /* #chars initally alloced */
#define STEP        8  /* #chars to realloc by */

#define END       (-1)  /* returned by getline to indicate EOF */
#define ALLOCFAIL    0  /* returned by getline to indicate allocation failure */
int getline(char **dynline)
{
    int i, c;
    size_t nalloced;  /* #chars currently alloced */

    if ((*dynline = malloc(INITALLOC)) == NULL)
        return ALLOCFAIL;

    nalloced = INITALLOC;
    for (i = 0; (c = getchar()) != EOF; ++i) {
        /* buffer is full, request more memory */
        if (i == nalloced)
            if ((*dynline = realloc(*dynline, nalloced += STEP)) == NULL)
                return ALLOCFAIL;

        /* store the newly read character */
        (*dynline)[i] = c;
    }
    /* zero terminate the string */
    (*dynline)[i] = '\0';

    if (c == EOF)
        return END;
    return i+1;  /* on success, return #chars read successfully 
                    (i is an index, so add 1 to make it a count */
}

这个函数动态分配内存,所以调用者需要free内存。

这段代码并不完美。如果在重新分配时出现故障,NULL 会覆盖之前完好的数据,从而导致内存泄漏和数据丢失。

【讨论】:

  • 没错,这是一个很好的答案
  • 太糟糕了,你使用了getline 名称,它在 POSIX 上具有不同的含义
  • 为什么要投反对票? 我认为您需要更像int getline(char **dynline) 的东西,这样调用者就会有一个指针,您可以修改它的值。现在,您正在以未定义的方式修改 char 的值。我实际上会返回指针并传递int 的地址以获得返回状态:char *myGetline( int *retStatus );
  • 已经指出comment
  • 你在浪费返回值;您应该使用它来指示读取了多少个字符。您在分配失败时出现内存泄漏;你刚刚用空指针覆盖了*dynline;可悲的是,没有其他方法可以释放先前分配的内容。研究POSIX的设计getline();它有很多优点。
【解决方案2】:

如果遇到换行符并返回fgets,您可以根据需要再次运行它多次以读取任意数量的行。循环对此很有用。

如果遇到 EOF,则说明您已到达文件的末尾(/stream),没有必要再次运行它,因为没有什么可读取的了。

下面是一个示例,展示了从标准输入将整个字符串读取到 EOF 的逻辑。

有很多方法可以做到这一点,这只是一种,但它显示了一般逻辑。

结果缓冲区会随着输入的读取而增长,并且对此没有限制 - 因此,如果从未达到 EOF,您最终将耗尽内存并且程序将退出。一个简单的检查可以避免这种情况,或者根据您的应用程序,您可以在数据进入时对其进行处理,而无需全部存储。

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

#define LINE_BUFFER_SIZE 256

// Each time this is exhausted, the buffer will be increased in size by this amount again.
#define INITIAL_BUFFER_SIZE 2048

int main (int argc, char **argv) {
    char *result = malloc(INITIAL_BUFFER_SIZE);
    if (!result) {
        // Out of memory.
        return 1;
    }

    size_t totalBytesRead = 0;
    size_t bytesAllocated = INITIAL_BUFFER_SIZE;

    char buf[LINE_BUFFER_SIZE];
    while (fgets(buf, LINE_BUFFER_SIZE, stdin)) {
        size_t bytesRead = strlen(buf);
        size_t bytesNeeded = totalBytesRead + bytesRead + 1;
        if (bytesAllocated < bytesNeeded) {
            char *newPtr = realloc(result, bytesAllocated + INITIAL_BUFFER_SIZE);
            if (newPtr) {
                result = newPtr;
                bytesAllocated += INITIAL_BUFFER_SIZE;
            }
            else {
                // Out of memory.
                free(result);
                return 1;
            }
        }

        memcpy(result + totalBytesRead, buf, bytesRead);
        totalBytesRead += bytesRead;
    }
    result[totalBytesRead] = '\0';

    // result contains the entire contents from stdin until EOF.

    printf("%s", result);

    free(result);
    return 0;
}

【讨论】:

  • 好的,但是我应该怎么做呢? while(what?) fgets(sentence,33,stdin);
  • 来自man fgets:“如果在读取任何字符之前出现文件结束,则它们返回 NULL 并且缓冲区内容保持不变。”所以,例如你可以做while (fgets(buffer, BUFLEN, stdin) != NULL) { ... }。另一种常见的模式是 while (1) 循环,其中有一个 EOF 测试,其中 breaks 不在循环中。
  • 我注意到如果用户输入一个 32 个或更多字符的句子,fgets 不会停止。此外,它每次运行时都会覆盖字符串。我正在寻找一种方法来存储由多行组成的句子,而不仅仅是一个......
  • 我添加了一个示例,展示了从标准输入逐行读取整个文件的逻辑,将行存储到缓冲区中,缓冲区会随着您的阅读而增长。
【解决方案3】:

在 POSIX 系统上,您有 getline。它能够在堆分配的内存中读取任意宽的行(直到耗尽资源)。

你也可以反复调用fgetc ...(顺便说一句,你应该准确地定义什么是你的字符串)

在 Linux 上,您可以使用GNU readline 从终端读取可编辑 行(即stdin,当它是tty)。

要读取一些类型的字符串,您可以使用fscanf,例如%50s%[A-Z] 等...

您可以使用fread读取一个数组(字节或其他二进制数据)

你可能会读一整行,然后parse它(也许使用sscanf)。您可以从中读取几行并在 heap memory 中构建一些字符串(例如,在具有它的系统上使用 asprintfstrdup)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-02-03
    • 2010-12-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-23
    • 2023-03-08
    相关资源
    最近更新 更多