使用fgets和sscanf意外重复

时间:2016-03-26 02:37:28

标签: c fgets scanf

这是我的代码的一部分。 getssscanf的目的是扫描由一个空格分隔的三个变量。如果通过,则再次输出指令。否则,输出错误并退出程序。

我想使用7长度字符数组来限制行中的数字,得到格式如' g 3 3'只要。但是我的代码似乎有些不对劲。

#include <stdio.h> 

int main (void) {
    char line[7];
    char command;
    int x, y;

    while(1){
        /* problem: g  4 4 or g 4  4 can also pass */
        fgets(line, 7, stdin);
        nargs = sscanf(line, "\n%c %d %d", &command, &x, &y);

        if(nargs != 3){
          printf("error\n");
          return 0;
        }

        printf("%c %d %d\n", command, x, y);
    }
}

意外:

g  4 4
g 4 4
error

预期:

g 4 4
g 4 4
// I can continue type

有谁能告诉我它为什么还会重复这条指令?

4 个答案:

答案 0 :(得分:2)

根据C11 standard, 7.21.6.2p5

  

由空白字符组成的指令通过读取第一个非空白字符(仍然未读取)的输入来执行,或者直到不再能够读取字符为止。

这描述了\n指令和两个空格字符在功能上是相同的:它们可以从输入中匹配尽可能多的连续空格(空格,制表符,换行符等)

如果你想匹配一个空格(并且只有一个空格),我建议使用%*1[ ]而不是white-space指令。您可以使用%*1[\n]同样丢弃换行符。例如,由于换行符出现在行尾的

nargs = sscanf(line, "%c%*1[ ]%d%*1[ ]%d%*1[\n]", &command, &x, &y);

不幸的是,这不能完全解决您的问题the %d format specifier is also defined to discard white-space characters

  

跳过输入空白字符(由isspace函数指定),除非规范包含[cn说明符

通过一些聪明的黑客,您可以继续使用sscanf(或者更好,scanf没有中间缓冲区),但在比较可维护性成本方面的替代方案后,我们可能会也可以使用getchar,因此,如果您正在寻找问题的解决方案,而不是回答您提出的问题,我建议gsamaras answer

答案 1 :(得分:1)

如果用户输入一个或两个空格,sscanf()将不会被打扰,因此您所拥有的内容将无效。

您可以通过利用short circuitinggetchar()这样简单的方式解决这个问题:

#include <stdio.h>
#include <ctype.h>

#define SIZE 100

int main(void) {
    int c, i = 0;
    char line[SIZE] = {0};
    while ((c = getchar()) != EOF) {
        // is the first char an actual character?
        if(i == 0 && !isalpha(c)) {
                printf("error\n");
                return -1;
        // do I have two whitespaces in 2nd and 4th position?
        } else if((i == 1 || i == 3) && c != ' ') {
                printf("error\n");
                return -1;
        // do I have digits in 3rd and 5th position?
        } else if((i == 2 || i == 4) && !isdigit(c)) {
                printf("error\n");
                return -1;
        // I expect that the user hits enter after inputing his command
        } else if(i == 5 && c != '\n') {
                printf("error\n");
                return -1;
        // everything went fine, I am done with the input, print it
        } else if(i == 5) {
                printf("%s\n", line);
        }
        line[i++] = c;
        if(i == 6)
                i = 0;
    }
    return 0;
}

输出:

gsamaras@gsamaras:~$ gcc -Wall px.c
gsamaras@gsamaras:~$ ./a.out 
g 4 4
g 4 4
g  4 4
error

答案 2 :(得分:1)

  

有谁能告诉我它为什么还会重复这条指令?

棘手的部分是"%d"消耗领先的空白区域,因此代码需要先检测前导空白区域。

" "消耗0或更多空白区域,永不失败。

所以"\n%c %d %d"没有很好地检测到插入空间的数量。

如果int s可以超过1个字符,请使用此字符,否则请参阅以下简化。

使用"%n检测sscanf()进度缓冲区中的位置。

使用显然需要的sscanf()完成工作。

// No need for a tiny buffer
char line[80];
if (fgets(line, sizeof line, stdin) == NULL) Handle_EOF();

int n[6];
n[5] = 0;
#define SPACE1 "%n%*1[ ] %n"
#define EOL1   "%n%*1[\n] %n"

// Return value not checked as following `if()` is sufficient to detect scan completion.
// See below comments for details
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" EOL1, 
  &command, &n[0], &n[1],
  &x,       &n[2], &n[3],
  &y,       &n[4], &n[5]);

// If scan completed to the end with no extra
if (n[5] && line[n[5]] == '\0') {
  // Only 1 character between?
  if ((n[1] - n[0]) == 1 && (n[3] - n[2]) == 1 && (n[5] - n[4]) == 1) {
    Success(command, x, y);
  }
}

也许添加测试以确保command不是空格,但我认为无论如何都会在命令处理中发生。

如果int s必须只有1位数并且mod与@Seb结合使用,则可以进行简化。这是有效的,因为每个字段的长度都以可接受的答案固定。

// Scan 1 and only 1 space
#define SPACE1 "%*1[ ]"

int n = 0;
// Return value not checked as following `if()` is sufficient to detect scan completion.
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);

// Adjust this to accept a final \n or not as desired.
if ((n == 5 && (line[n] == '\n' || line[n] == '\0')) {
  Success(command, x, y);
}

@Seb和我需要检查sscanf()的返回值。虽然cnt == 3测试是多余的,因为n == 5仅在扫描完整行并且sscanf()返回3时才为真,但是许多代码检查器可能会引发一个标记,指出{的结果{未检查{1}}。在使用保存的变量之前不对sscanf()的结果进行限定并不是健壮的代码。这种方法使用sscanf()的简单和充分的检查。由于许多代码问题源于没有进行任何资格认证,因此缺少对n == 5的检查可能会在代码检查器中产生误报。很容易添加冗余检查。

sscanf()

答案 3 :(得分:0)

你有程序问题吗? gdb是你最好的朋友=)

gcc -g yourProgram.c
gdb ./a.out
break fgets
run
finish
g 4  4

然后逐步执行语句,每当遇到scanf或printf只是输入完成时,你会看到程序是否成功完成了这个迭代但是程序没有等待输入而只是打印错误信息?为什么?井型:

man fgets

fgets读取最多只比大小少一个,所以在你的情况下,fgets只允许读取6个字符,但你给它7个!是的,换行符就像空格一样,所以7号会发生什么?它将被缓冲,这意味着您的程序将不会从键盘读取,而是会看到缓冲区中有字符并将使用它们(本例中为一个字符)。 编辑:您可以采取以下措施使您的计划正常工作 您可以忽略空行,if(strccmp(line,&#34; \ n&#34;)== 0)然后跳转到下一次迭代,如果不允许使用strcmp,则解决方法是比较line [0 ] ==&#39; \ n&#39;