【问题标题】:How is input parsed in C?在 C 中如何解析输入?
【发布时间】:2013-07-16 03:16:55
【问题描述】:

帖子末尾的完整、调整后的代码

如果我要让用户输入并使用 scanf() 获取它,这个过程是如何工作的(关于缓冲区等的细节)?

人们提到“刷新”缓冲区是什么意思?我还听说刷新输入(或者,更确切地说,未定义?)是不好的做法,但是刷新输出流是创建 fflush() 之类的函数的目的。 -- 上述上下文中的“流”是什么意思?

所以,在程序上,如果我要求输入如下:

printf("Enter a string: ");
    scanf("%s", string);

输入会发生什么;字符串在哪里被 scanf 函数“抓取”?

输入缓冲区中的“空白”是什么? (空格字符、NULL 等?)


在我看来,虽然我读过的文献从未专门解决这些问题,但我认为来自用户或来自 FILE 的输入将存储在某个临时 char 数组中,该数组可供访问和打印/存储使用适当的指针。

之所以提出这个问题是因为我对以下代码有一个相关问题:

int main(){
    char string[20];
    char string2[20];

//strlen test
    printf("Enter a string: ");
        scanf("%s", string);
    printf("\t length: %d\n", strlen(string));

//strcat test
    printf("Enter two strings to concatentate: ");
        scanf("%s %s", string, string2);
    strcat(string2, string);
    printf("\nConcatenated: %s\n\n\n", string);

return 0;

}

顺便说一句:上面的 strlen() 和 strcat() 函数是在本地定义的,因此参数可能与您熟悉的 C 库中的不匹配。

产生以下输出:

我假设,一旦 scanf 函数遇到空格,它就会假定字符串的结尾。虽然,剩余的输入仍然存在于缓冲区中。然后,当我要求更多输入时,输入的数据被放置在缓冲区的末尾。因此,当这次调用 scanf() 时,将“andothernonsensehere”作为下一个输入,因为它较早地存在于缓冲区中。

虽然,如果我上面说的是真的,不应该将第二个“andothernonsensehere”字符串和在第二次调用中输入的第一个字符串“sherrell”连接起来吗?


tl;博士版本 如何刷新缓冲区以确保 scanf() 捕获下一个输入事件?


完整、已编辑、代码:

#include <stdio.h>

int main(){
    char string[20];
    char string2[20];

//strlen test
    printf("Enter a string: ");
        scanf("%s", string);
    printf("\t length: %d\n", strlen(string));
    clear(); //make sure buffer is empty

//strcat test
    printf("Enter two strings to concatentate: ");
        scanf("%s %s", string, string2);

    strcat(string2, string);
    printf("\nConcatenated: %s\n\n\n", string);

    return 0;
}

void strcat(char *toCopy, char *org){
    while(*org != NULL) org++; //find end of characters
    while( (*org++ = *toCopy++) != NULL); //copy
}

int strlen(char *a){
    char *b = a;
    while(*b++ != NULL);
    return b-a;
}


void clear(){
    while(getchar() != '\n');
}

【问题讨论】:

    标签: c parsing input buffer


    【解决方案1】:

    你基本上做对了,虽然缓冲可能不是你想的那样。

    scanf 不维护缓冲区。从概念上讲,它一次读取一个字符,直到格式或输入用尽。

    但是,终端输入通常由终端驱动程序缓冲。或者,更准确地说,如果没有等待读取的字符,则在按下 Enter 键之前,终端输入请求不会返回任何内容,即使程序只读取一个字符。未读字符保留在内核中,它们将根据要求提供给用户程序。 (终端驱动程序还处理输入字符时的回显,以及处理退格和许多其他事情。)但是,所有这些行为都可以更改。有关详细信息,请参阅 man sttyman termios(可能还有 man tty_ioctl)。很多。

    所以scanf 完全不知道接下来会发生什么。它只是消耗字符直到满足为止,并且如果需要,调用ungetc 以返回它读取但不需要的最后一个字符。

    现在,你的问题:

    不应该将第二个“andothernonsensehere”字符串和第二次调用时输入的第一个字符串“sherrell”连接起来吗?

    回答:是的,他们应该这样做。它们将与标准库函数 strcat 一起使用,前提是您将其参数按正确的顺序放置并确保 string 有足够的空间来容纳串联和终止 NUL 字符。

    正如您所说,我无法对您的strcat 做出任何假设,因此我不知道它的参数顺序与标准库版本的顺序相同。但是,如果确实如此,那么您看到的行为是可以预料的:strcat 会将string 附加到string2 的末尾(覆盖随机内存,因为string2 不足以容纳串联),但这不会改变string(除非string 是被覆盖的随机内存)。因此,当您打印出 string 时,您会看到它原来的样子,即一个 20 字符的字符串,其终止 NUL 字节也被其他一些随机内存位置覆盖。

    随着所有未定义行为的发生,您的程序几乎可以产生任何输出,或者它可能会出现段错误或触发nasal demons。我认为您的 C 编译器可能在双字边界上对齐字符串,因为我希望 string2 立即跟随 string,因此在您将 20 个字符放入 string 之后尾随 NUL 将是第一个string2 的字节,然后当 scanf 填充 string2 时将被覆盖。此时将string 附加到string2 的后果将是,嗯,很有趣。

    【讨论】:

    • 谢谢。这个概念现在更有意义了。我在 OP 的末尾添加了完整的代码,包括我的 strcat() 版本。有什么建议吗?
    • @sherrellbc:我的建议:使用-Wall(至少),并使stringstring2 更大(或使用更短的输入)。我通过以下方式修改了您的代码:将 NULL 更改为 0(NULL 是指针);将 strlen、strcat 和 clear 放在开头,并更改它们的名称以避免与 gcc 内置冲突;并将两个缓冲区更改为 40 个字符。它工作得很好。 (gcc 仍然正确警告忽略 scanf 的返回值。您应该始终检查 scanf 的返回值。)
    • 函数放置是否值得关注? (即主要之前/之后)。我总是以这样一种方式进行编程,即出于习惯包含函数原型(尽管不是在这里)。我知道,如果您有一个函数调用另一个在其下方定义的函数,那么调用函数将不会“看到”定义的函数并且程序将无法编译。我很惊讶地看到我编写的代码因为缺少函数原型以及 main 调用的是在它自己的定义之后定义的函数而被编译。
    • 另外,我在 OP 末尾发布的代码在我添加了诸如 clear() 函数之类的功能后才能工作,该函数会删除缓冲区中的任何多余输入。我对您的建议的评论是针对您对我的 strcat() 实现与库版本相比的担忧。
    • @sherrellbc:啊。除了在您的意思是 0 的地方使用 NULL 之外,我认为这对于短字符串来说不是问题。它遭受与内置函数完全相同的不安全性。内置可能会更快地闪击内存,因为它将使用向量操作和双字读/写以及他们提出的任何其他技巧。实际上,最大的问题是缓冲区溢出。我建议您实现 strncat 而不是 strcat,并且将参数按相同的顺序放置(否则,这会让读者感到困惑)。但见codereview.stackexchange.com
    【解决方案2】:
        String2
    

    未声明,其输入为“”空字符串。因此,您输入第一个并与空字符串连接会产生与原始输入相同的字符串 %s 是“输入”,其中 s 代表字符串,您将其存储在字符串中。你从哪里获得输入。

    【讨论】:

    • 我没有发布整个代码。这是我上面列出的问题显而易见的示例。
    • 这个答案与我的问题完全无关。我知道 scanf() 函数的工作原理。
    • scanf("%s %s", string, string2);这一行是它给你的问题
    • 有什么问题?我将我的问题归咎于这一行: scanf("%s", string); 因为,鉴于我在上图中显示的输入,输入中基本上存在两个由空格分隔的字符串。输入,直到遇到空格,都存储在字符串中。当调用第二个 scanf() 时,scanf("%s %s", string, string2),输入缓冲区的下一个可用字符串(在下一个空格或转义字符之前,是 ' andothernonsensehere' 字符串显示在图像中。因此,解释为什么打印它。如果我错了,请解释。
    • 试试这个scanf("%s %s", string, string2);strcat(string2, " ");strcat(string2, string);
    猜你喜欢
    • 1970-01-01
    • 2010-09-18
    • 2015-04-22
    • 1970-01-01
    • 2016-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多