我的代码可以吗还是会在某些情况下失败?

Is my code okay or will it fail in some occasion?

我最近了解了键盘缓冲区以及在获得用户输入后清理它的重要性。所以我做了这个函数来处理:

// Get input from user
void get_input(char *buff, int size)
{
    fgets(buff, size, stdin);
    int newlineIndex = strcspn(buff, "\n");

    /* If no newline is found, fgets stopped at `size` and
    didn't read all input. Keyboard buffer is dirty */
    if (newlineIndex == strlen(buff))
    {
        // Clear keyboard buffer
        int c;
        while ((c = getchar()) != '\n' && c != EOF);
    }
    else
    {
        // Remove newline from buff
        buff[newlineIndex] = '[=10=]';
    }
}

此处,buff 是您要存储输入的位置,sizesizeof(buff)。在使用 fgets 读取用户的输入后,我用 strcspn() 查找 fgets 留下的 \n 字符。如果 newlineIndex == strlen(buff),未找到 \n 换行符,这意味着用户键入的字符多于 size,因此我继续使用以下命令清除键盘缓冲区:

int c;
while ((c = getchar()) != '\n' && c != EOF);

否则,键盘缓冲区没有变脏,所以我从用户输入中删除了 \n,以便以后更轻松地将其作为字符串处理。
我的代码没问题还是我遗漏了什么?有没有更有效的方法呢?

是的,你的代码没问题,应该不会失败。但是总是检查 fgets 是否失败:(来自手册)

If an error occurs, they return NULL and the buffer contents are indeterminate.

它比必要的更复杂和低效,尤其是考虑到 strings work in C 的方式。您不需要搜索 newline。它要么在字符串的末尾,要么不在 - 您唯一需要查看的地方是在字符串的末尾。

fgets() 可以 return NULL 如果例如在读取任何字符之前遇到 EOF - 如果使用来自文件的输入重定向,这很容易发生。所以你应该检查一下。您的代码 以不可预测的方式失败 as-is if buff[0] != '[=17=]' on entry and fgets() returns NULL.

它通常对 return 某些东西 有用 - 即使您经常丢弃 return 值。您可以 return 错误指示或 return 有效(但为空)的错误字符串,或者可能 return 输入字符串的长度以保存调用者另一个 strlen()遍历。我个人建议 returning 指向调用者缓冲区的指针并初始化 buff[0] = '[=21=]' 以确保始终 returned 有效字符串。这样您就可以执行以下操作:

char inp[20] ;
printf( "%s\n", get_input( inp, sizeof(inp) ) ) ;

所以我建议:

char* get_input( char* buff, size_t size )
{
    buff[0] = '[=11=]' ;
    
    if( fgets(buff, (int)size, stdin) != NULL )
    {
        size_t len = strlen( buff ) ;
        char* endp = len == 0 ? buff : &buff[len - 1] ;
        
        if(  *endp != '\n' )
        {
            int discard ;
            while( (discard = getchar()) != '\n' && discard != EOF ) ;
        }
        else
        {
            // Remove newline from buff
            *endp = '[=11=]';
        }
    }
    
    return buff ;
}

一个可能有用的修改,因为您需要确定输入的长度的麻烦是 return 通过参考参数给调用者的长度:

// Get input from user
char* get_input( char *buff, size_t size, size_t* inplen )
{
    buff[0] = '[=12=]' ;
    size_t len = 0 ;
    
    if( fgets(buff, (int)size, stdin) != NULL )
    {
        len = strlen( buff ) ;
        char* endp = len == 0 ? buff : &buff[len - 1] ;
        
        if(  *endp != '\n' )
        {
            int discard ;
            while( (discard = getchar()) != '\n' && discard != EOF ) ;
        }
        else
        {
            // Remove newline from buff
            *endp = '[=12=]';
            len-- ;
        }
    }
    
    // Return string length via inplen if provided
    if( inplen != NULL ) *inplen = len ;
    
    return buff ;
}

然后例如:

char inp[20] ;
size_t length = 0 ;
printf( "%s\n", get_input( inp, sizeof(inp), &length ) ) ;
printf( "%zu characters entered\n", length ) ;

或者如果你想丢弃长度:

get_input( inp, sizeof(inp), NULL ) ;

.... it fail in some occasion?

是的。

  1. 缺少对 fgets() 中 return 值的检查。 fgets() returns NULL 表示立即 end-of-file 或刚刚发生输入错误,get_input() 也应如此。在这种情况下,对于 OP 的代码,buff[] 处于不确定状态,因此 strcspn(buff, "\n"); 存在 未定义行为 (UB) 的风险。

  2. 代码无法 return 任何成功或错误的指示(end-of-file,此处发生的输入错误或过长的行。)。

  3. 极端 缓冲区大小可能超过int 范围。 size_t size 不会超过数组或分配大小。 strcspn(buff, "\n") return 是 size_t,而不是 int。如果值大于 INT_MAX.

    ,保存在 int 中将导致问题
  4. 如果读取了先前的 空字符
  5. strcspn(buff, "\n") 无法检测到 '\n'

标准 C 缺乏将 读入字符串的可靠方法。 fgets() 让我们大部分时间都在那里。