【问题标题】:How to take a line input in C?如何在C中输入一行?
【发布时间】:2025-11-26 04:50:01
【问题描述】:

我试图在 C 中输入一个完整的行。最初我这样做了,

char line[100] // assume no line is longer than 100 letters.
scanf("%s", line);

忽略安全漏洞和缓冲区溢出,我知道这永远不会超过一个单词输入。我又修改了,

scanf("[^\n]", line);

当然,这不能超过一行输入。然而,下面的代码却陷入了无限循环,

while(fscanf(stdin, "%[^\n]", line) != EOF)
{
    printf("%s\n", line);
} 

这是因为,\n 从未被消耗过,并且会反复停在同一点并在 line 中具有相同的值。所以我将代码重写为,

while(fscanf(stdin, "%[^\n]\n", line) != EOF)
{
    printf("%s\n", line);
}

这段代码工作无可挑剔(或者我是这么认为的),用于文件输入。但是对于来自stdin 的输入,这会产生神秘、怪异、口齿不清的行为。只有在输入第二行后,才会打印第一行。我无法理解到底发生了什么。

我正在做的就是这个。记下字符串,直到遇到\n,将其存储在line 中,然后从输入缓冲区中使用\n。现在打印这个line 并准备好输入下一行。还是我被误导了?

然而,在发布这个问题时,我找到了一个更好的选择,

while(fscanf(stdin, "%[^\n]%*c", line) != EOF)
{
    printf("%s\n", line);
}

这适用于所有情况。但我的问题仍然存在。这段代码怎么来的,

while(fscanf(stdin, "%[^\n]\n", line) != EOF)
{
    printf("%s\n", line);
}

适用于来自文件的输入,但会导致来自标准输入的输入出现问题?

【问题讨论】:

  • 使用 fgets(),而不是 fscanf()。
  • 我也会选择fgets - 不要忘记它会保留任何尾随newline。至于scanf 家族,请始终检查正确的转换的项目数0 转换的项目不会被 EOF 测试检测到,但如果没有转换正确数量的项目,EOF 会检测到。
  • 不要爬山,绕着它走。 scanf 家庭停滞不前时很可怕。最好使用fgets 阅读该行,然后然后 使用sscanf
  • 您的 sn-ps 仍在测试返回的 EOF。这只会在任何错误输入时停止。如果您正在读取文件并且没有获得正确的数据,那么您不妨认输——GIGO——但测试EOF 会导致停顿。坏数据留在那里等待以某种方式读取或删除,但它不是文件的结尾。 while(scanf("...", ...) == numberofitems) {...}
  • @FredK,永远不要使用gets() 1) 它会导致输入缓冲区溢出,2) 它不再是 C 语言的一部分——现代编译器会警告你不要使用 @987654347 @

标签: c file-io stdin scanf


【解决方案1】:

使用fgets()@FredK

char buf[N];
while (fgets(buf, sizeof buf, stdin)) {
  // crop potential \n if desired.
  buf[strcspn(buf, "\n")] = '\0'; 
  ...
}

尝试将scanf() 用于用户输入时存在许多问题,这使其容易被误用或代码攻击。

// Leaves trailing \n in stdin
scanf("%[^\n]", line)

// Does nothing if line begins with \n. \n remains in stdin
// As return value not checked, use of line may be UB.
// If some text read, consumes \n and then all following whitespace: ' ' \n \t etc.
//    Then does not return until a non-white-space is entered.
//    As stdin is usually buffered, this implies 2 lines of user input.
// Fails to limit input.
scanf("%[^\n]\n", line)

// Does nothing if line begins with \n. \n remains in stdin
// Consumes 1 char after `line`, even if next character is not a \n
scanf("%99[^\n]%*c", line)

检查 EOF 通常是 错误 检查。 @Weather Vane 以下,当 first 输入 \n 时,返回 0,因为 line 未填充。作为0 != EOF,代码继续使用未初始化的line 导致UB。

while(fscanf(stdin, "%[^\n]%*c", line) != EOF)

考虑在下面输入“1234\n”。可能无限循环,因为第一个 fscanf() 读取“123”,抛出“4”,下一个 fscanf() 调用卡在 \n 上。

while(fscanf(stdin, "%3[^\n]%*c", line) != EOF)

检查*scanf() 的结果时,检查您想要的,而不是您不想要的值之一。 (但即使是以下还有其他麻烦)

while(fscanf(stdin, "%[^\n]%*c", line) == 1)

关于最近的scanf() 读取一行

char buf[100];
buf[0] = 0;
int cnt = scanf("%99[^\n]", buf);
if (cnt == EOF) Handle_EndOfFile();
// Consume \n if next stdin char is a \n
scanf("%*1[\n]");
// Use buf;

while(fscanf(stdin, "%[^\n]%*c", line) != EOF)
为来自文件的输入工作,但导致来自标准输入的输入出现问题?

发布示例代码和输入/数据文件会很有用。发布的代码量不大,有一些潜在的原因。

line 溢出是 UB
输入以\n 开头,指向 UB
文件或stdin 未以相同模式打开。 \r 没有翻译成一个。


注意:当一行是 100 个字符时,以下操作会失败。所以满足假设 cal 仍然会导致 UB。

char line[100] // assume no line is longer than 100 letters.
scanf("%s", line);

【讨论】:

    【解决方案2】:

    就个人而言,我认为fgets() 的设计很糟糕。当我阅读一行时,我想完整地阅读它,而不管它的长度(填满所有 RAM 除外)。 fgets() 不能一口气做到这一点。如果一行很长,您必须手动运行多次,直到它到达换行符。 glibc 特有的getline() 在这方面更方便。这是一个模仿 GNU 的 getline() 的函数:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    long my_getline(char **buf, long *m_buf, FILE *fp)
    {
        long tot = 0, max = 0;
        char *p;
        if (*m_buf == 0) { // empty buffer; allocate
            *m_buf = 16;   // initial size; could be larger
            *buf = (char*)malloc(*m_buf); // FIXME: check NULL
        }
        for (p = *buf, max = *m_buf;;) {
            long l, old_m;
            if (fgets(p, max, fp) == NULL)
                return tot? tot : EOF; // reach end-of-file
            for (l = 0; l < max; ++l)
                if (p[l] == '\n') break;
            if (l < max) { // a complete line
                tot += l, p[l] = 0;
                break;
            }
            old_m = *m_buf;
            *m_buf <<= 1; // incomplete line; double the buffer
            *buf = (char*)realloc(*buf, *m_buf); // check NULL
            max = (*m_buf) - old_m;
            p = (*buf) + old_m - 1; // point to the end of partial line
        }
        return tot;
    }
    
    int main(int argc, char *argv[])
    {
        long l, m_buf = 0;
        char *buf = 0;
        while ((l = my_getline(&buf, &m_buf, stdin)) != EOF)
            puts(buf);
        free(buf);
        return 0;
    }
    

    我通常使用自己的 readline() 函数。我刚才写了这个my_getline()。它没有经过彻底的测试。请谨慎使用。

    【讨论】:

    • 请注意,*nix 使用ssize_t getline (char **lineptr, size_t *n, FILE *stream) 使用size_t*,而不是long *。此代码更改的任何特殊原因?顺便说一句:代码有泄漏:free(*buf) 之前应该是 *buf = malloc(*m_buf); l = strlen(p); if (p[l-1]... 是黑客攻击,因为从 fgets() 返回的第一个字符可能是空字符。
    • 没有泄漏。如果buf 是预分配的,*m_buf 应该是正数。那么你永远不会遇到malloc。至于size_t等,请随时更改。上面只是显示了一个草图。对于正确的库函数,我们至少需要检查*alloc()fgets() 返回的第一个字符不应为 NULL(哦,等等,除非 *m_buf 等于 1)。
    • *buf 可能是非NULL*m_buf 可能是0。long m_buf = 0; char *buf = malloc(m_buf); my_getline(&amp;buf, &amp;m_buf, stdin); IAC,它是一个极端情况。仅供参考
    • 不,那是滥用功能。如果m_buf=0buf 未初始化,则最终会出现段错误。没有有效的方法来防止各种滥用。
    • "first char fgets() 返回不应为 NULL" 为什么不呢?黑客键入空字符的键序列,然后 。这会导致"\0\n\0" 并破坏此代码。