如何防止scanf导致C中的缓冲区溢出?

goe*_*goe 75 c scanf overflow

我用这个代码:

while ( scanf("%s", buf) == 1 ){
Run Code Online (Sandbox Code Playgroud)

什么是防止可能的缓冲区溢出的最佳方法,以便它可以传递随机长度的字符串?

我知道我可以通过调用例如限制输入字符串:

while ( scanf("%20s", buf) == 1 ){
Run Code Online (Sandbox Code Playgroud)

但我更愿意能够处理用户输入的任何内容.或者这不能使用scanf安全地完成,我应该使用fgets?

Jon*_*ler 57

在他们的" 编程实践"(非常值得一读)中,Kernighan和Pike讨论了这个问题,他们通过使用snprintf()创建具有正确缓冲区大小的字符串来解决它,以便传递给scanf()函数族.有效:

int scanner(const char *data, char *buffer, size_t buflen)
{
    char format[32];
    if (buflen == 0)
        return 0;
    snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
    return sscanf(data, format, buffer);
}
Run Code Online (Sandbox Code Playgroud)

注意,这仍然将输入限制为"缓冲区"提供的大小.如果您需要更多空间,则必须进行内存分配,或使用为您进行内存分配的非标准库函数.


注意的POSIX 2008(2013)版本scanf()系列的功能支持的格式修改m字符串输入(分配分配字符)( ,%s,).%c %[而不是采取一个char *参数,它需要一个char **参数,它分配的读取值所需的空间:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}
Run Code Online (Sandbox Code Playgroud)

如果sscanf()函数无法满足所有转换规范,则%ms在函数返回之前释放它为类似转换分配的所有内存.


Joh*_*ter 30

如果你正在使用gcc,你可以使用GNU扩展a说明符让scanf()为你保存输入分配内存:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

编辑:正如Jonathan指出的那样,您应该查阅scanf手册页,因为说明符可能不同(%m),并且您可能需要在编译时启用某些定义.

  • 这更像是使用glibc(GNU C库)而不是使用GNU C编译器的问题. (7认同)
  • GNU(无论如何在Ubuntu 13.10上都可以找到)支持`%ms`.符号'%a`是`%f`的同义词(在输出时,它请求十六进制浮点数据).`scanf()`的GNU手册页说:_如果程序是用`gcc -std = c99`或gcc -D_ISOC99_SOURCE编译的,则不可用(除非还指定了`_GNU_SOURCE`),在这种情况下``a `被解释为浮点数的说明符(见上文)._ (3认同)
  • 并注意POSIX 2008标准提供了`m`修饰符来完成相同的工作。参见[`scanf()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/scanf.html)。您需要检查您使用的系统是否支持此修饰符。 (2认同)

dir*_*tly 9

大部分时间都是工作的组合fgetssscanf工作.如果输入格式正确,另一件事就是编写自己的解析器.另请注意,您的第二个示例需要进行一些修改才能安全使用:

#define LENGTH          42
#define str(x)          # x
#define xstr(x)         str(x)

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 
Run Code Online (Sandbox Code Playgroud)

以上将输入流丢弃但不包括newline(\n)字符.你需要添加一个getchar()来消费它.还要检查您是否到达了流末尾:

if (!feof(stdin)) { ...
Run Code Online (Sandbox Code Playgroud)

这就是它.

  • 你能把 `feof` 代码放到更大的上下文中吗?我问是因为该功能经常被错误使用。 (2认同)

Dig*_*oss 4

直接使用scanf(3)及其变体会带来许多问题。通常,用户和非交互式用例是根据输入行来定义的。如果没有找到足够的对象,则很少会出现更多行可以解决问题的情况,但这是 scanf 的默认模式。(如果用户不知道在第一行输入数字,第二行和第三行可能没有帮助。)

至少如果您fgets(3)知道程序需要多少输入行,并且不会出现任何缓冲区溢出......