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

如何防止scanf在C中导致缓冲区溢出?,c,scanf,buffer-overflow,C,Scanf,Buffer Overflow,我使用以下代码: while ( scanf("%s", buf) == 1 ){ 防止缓冲区溢出的最佳方法是什么,这样就可以传递随机长度的字符串 我知道我可以通过调用来限制输入字符串,例如: while ( scanf("%20s", buf) == 1 ){ 但我更希望能够处理用户输入的任何内容。 或者说,使用scanf不能安全地完成这项工作吗?我应该使用fgets吗?直接使用scanf(3)及其变体会带来许多问题。通常,用户和非交互用例是根据输入行定义的。很少看到这样的情况,如果没有找

我使用以下代码:

while ( scanf("%s", buf) == 1 ){
防止缓冲区溢出的最佳方法是什么,这样就可以传递随机长度的字符串

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

while ( scanf("%20s", buf) == 1 ){
但我更希望能够处理用户输入的任何内容。 或者说,使用scanf不能安全地完成这项工作吗?我应该使用fgets吗?

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


至少如果你知道你的程序需要多少输入行,并且你不会有任何缓冲区溢出…

限制输入的长度肯定更容易。您可以通过使用循环接受任意长的输入,一次读取一位,根据需要重新分配字符串的空间


但是这是一个很大的工作,所以大多数C程序员只是在一些任意长度上切掉输入。我想您已经知道这一点,但是使用fgets()不允许您接受任意数量的文本-您仍然需要设置一个限制。

大多数情况下,
fgets
sscanf
的组合起作用。另一件事是编写自己的解析器,如果输入格式正确的话。另请注意,您的第二个示例需要进行一些修改才能安全使用:

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

/* ... */ 
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 
上述操作将丢弃最多但不包括换行符(
\n
)字符的输入流。您需要添加一个
getchar()
来使用它。还要检查您是否到达流的末尾:

if (!feof(stdin)) { ...

这就是问题所在。

如果您使用的是gcc,您可以使用GNU扩展名
a
说明符让scanf()为您分配内存来保存输入:

int main()
{
  char *str = NULL;

  scanf ("%as", &str);
  if (str) {
      printf("\"%s\"\n", str);
      free(str);
  }
  return 0;
}
编辑:正如Jonathan指出的,您应该参考
scanf
手册页,因为说明符可能不同(
%m
),并且您可能需要在编译时启用某些定义。

在他们的书中(非常值得一读),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);
}
注意,这仍然将输入限制为作为“缓冲区”提供的大小。如果需要更多空间,则必须进行内存分配,或者使用非标准库函数为您进行内存分配


请注意,POSIX 2008(2013)版本的函数系列支持字符串输入的格式修饰符
m
(赋值分配字符)(
%s
%c
%[
)。它不采用
char*
参数,而是采用
char**
参数,并为读取的值分配必要的空间:

char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
    printf("String is: <<%s>>\n", buffer);
    free(buffer);
}
char*buffer=0;
如果(sscanf(数据,“%ms”和缓冲区)==1)
{
printf(“字符串为:\n”,缓冲区);
自由(缓冲);
}

如果
sscanf()
函数未能满足所有转换规范,那么在函数返回之前,它为类
%ms
转换分配的所有内存都将被释放。

创建一个为字符串分配所需内存的函数并不需要太多工作。 这是我不久前写的一个小c函数,我总是用它来读取字符串

如果出现内存错误,它将返回读取字符串或NULL。 但请注意,必须释放()字符串,并始终检查其返回值

#define BUFFER 32

char *readString()
{
    char *str = malloc(sizeof(char) * BUFFER), *err;
    int pos;
    for(pos = 0; str != NULL && (str[pos] = getchar()) != '\n'; pos++)
    {
        if(pos % BUFFER == BUFFER - 1)
        {
            if((err = realloc(str, sizeof(char) * (BUFFER + pos + 1))) == NULL)
                free(str);
            str = err;
        }
    }
    if(str != NULL)
        str[pos] = '\0';
    return str;
}

那么,有人知道如何使用scanf实现这一点吗?在循环中使用fgets可以允许您接受任意数量的文本-只需保持
realloc()
ing您的缓冲区。这更多的是使用glibc(GNU C库)的问题而不是使用GNU C编译器。请注意,POSIX 2008标准提供了
m
修饰符来完成相同的工作。请参阅。您需要检查您使用的系统是否支持此修饰符。GNU(至少可以在Ubuntu 13.10上找到)支持
%ms
。符号
%a
%f
的同义词(在输出时,它请求十六进制浮点数据)。
scanf()
的GNU手册页上说:\如果程序是用
gcc-std=c99
或gcc-D_ISOC99_源代码编译的,则该手册页不可用(除非还指定了
\u GNU__
),在这种情况下,
a
被解释为浮点数的说明符(见上文)。@Sam:是的,它应该是
buflen-1
-谢谢。然后你必须担心无符号下溢(包装到一个相当大的数字),因此,
if
测试。我很想用
assert()替换它
,或者在开发过程中如果有人粗心地通过0作为大小,则在触发的
if
之前使用
assert()
对其进行备份。我没有仔细阅读文档了解
%0s
sscanf()
-如果(buflen<2),测试可能会更好。所以
snprintf
将一些数据写入字符串缓冲区,然后
sscanf
从创建的字符串中读取数据。这究竟在哪里取代了
scanf
,因为它从stdin中读取数据?对于结果字符串使用“format”一词,并因此传入“format”,这也是相当令人困惑的作为
snprintf
的第一个参数,但它不是实际的格式参数。@krb686:编写此代码是为了使要扫描的数据位于参数
data
中,因此
sscanf()
是合适的。如果要从标准输入读取,请删除
data
参数并调用
s