📜  为什么要在C中的scanf()上使用fgets()?

📅  最后修改于: 2021-05-25 21:48:49             🧑  作者: Mango

每当使用* f函数,无论是printfscanf还是它们的派生函数(fprintf,fscanf等),您所做的事情都比您想象的要多。您不仅在阅读(或写作)某些东西,而且(这里就是问题)您正在解释它。可以将格式字符串视为一种“ eval”函数,就像您在Lisp中编程一样。因此,与简单地从用户读取输入和呼应回来了,问题是一个恶毒的演员可以简单地插入一个函数指针或可执行代码,瞧,你在控制不再。

使用scanf()的优点:
用户不需要知道堆栈的大小,这是启动程序代码的一百个字节。很好,尽管任何人都可以坐在那里尝试越来越长的输入字符串,直到发生BufferOverflow错误。理想情况下,您只需要编写一个脚本即可自动尝试增加字符串大小并读取程序的退出代码。一旦检测到错误,您就可以简单地重新计算堆栈的估计值。任何使用内存地址随机化的现代操作系统都会使劫持应用程序的整个过程变得更加困难,但这绝不是不可能的。

fgets()超过scanf():

fgets函数是file-get-string的缩写。请记住,文件在* nix系统上几乎可以是任何东西(套接字,流或实际文件),因此我们可以使用它从标准输入中读取文件,从技术上来说,标准输入也是文件。这也使我们的程序更健壮,因为如源代码中所述,只需调整代码即可从命令行,文件或stdin读取,而不会带来太多麻烦。

至关重要的是,fgets函数还允许我们指定特定的缓冲区长度,从而禁止任何缓冲区溢出攻击。如果查看下面的完整代码,您会注意到默认缓冲区大小已定义为宏。请记住,C不能使用’const int’变量正确地初始化数组。您可以使用可变长度数组(VLA)破解它,但这并不理想,我强烈建议您反对它。因此,尽管在C++中我们通常会使用其他任何东西,但在这里我们确实使用了预处理器宏,但是请记住,在进行静态类型检查时,C和C++具有很大的不同功能,即C++优于C。 ()方法实际上可以处理I / O期间的错误

因此,实际读取用户输入的代码如下:

char* inputBuffer = malloc(sizeof(char) * DEFAULT_BUFFER_SIZE);
memset(inputBuffer, NUL, DEFAULT_BUFFER_SIZE);
  
char* result = NULL;
  
while (result == NULL) {
    result = fgets(inputBuffer, DEFAULT_BUFFER_SIZE, stdin);
  
    if (inputBuffer[strlen(inputBuffer) - 1] != '\n') {
        ErrorInputStringTooLong();
  
        // Since 'result' is the canary
        // we are using to notify of a failure
        // in execution, set it to NULL, to indicate an error.
        // This is a useful value because
        // if for some reason the fgets f/nction were
        // to fail, the return value would also be NULL.
  
        result = NULL;
    }
}

如预期的那样,我们动态分配预定大小的缓冲区。动态分配它而不是堆栈分配给我们提供了另一层保护,防止堆栈被破坏

注意:我们显式地将inputBuffer中的内存清零。 C对您不执行任何操作,并且malloc也不例外。对malloc的调用返回一个指向所请求内存的第一个存储单元的指针,但是与调用之前相比,这些单元中每个单元的值均未更改。出于所有意图和目的,它们都是垃圾,因此我们将其归零。还要注意,通过归零,我们自动为自己提供了null终止符,尽管fgets函数实际上确实在输入字符串的末尾附加了null,只要缓冲区中有足够的空间。

当用户输入太长的字符串:

您会注意到,我检查以确保最后读取的值不是换行符。如果是这样,则表示用户传入的输入字符串过长。为了解决这个问题,我们需要将’result’变量设置为NULL,以便我们再次循环执行循环,但是我们还需要清除输入缓冲区。否则,程序将简单地从尚未使用的旧输入中读取内容,而不提示用户进行其他输入。为了解决这个问题,我提供了两个附加功能。

static inline void ErrorInputStringTooLong()
{
  
    // NOTE: Print to stderr, not to stdout.
    fprintf(stderr, "[ERROR]: The input was too long, please try again.\n");
  
    // Having notified the user,
    // clear the input buffer and return.
    ClearInputBuffer();
}
  
static inline void ClearInputBuffer()
{
  
    // This variable is merely here to munch the garbage out of the input
    // buffer. As long as the input buffer's current character is not either
    // a new line character or the end of input, keep reading characters. This
    // seems to be the only portable way of clearing the input buffer.
    char c = NUL;
  
    while ((c = getchar()) != '\n' && c != EOF) {
        // Do nothing until input buffer fully flushed.
    }
}

注意:通常,要清除输入缓冲区,只需调用fseek(stdin,0,SEEK_END);但它似乎并非在所有平台上都适用。因此,使用上述方法。

注意:如果fgets返回错误,则应调用ferror()找出问题所在。

想要从精选的最佳视频中学习和练习问题,请查看《基础知识到高级C的C基础课程》。