91

我使用这段代码:

while ( scanf("%s", buf) == 1 ){

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

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

while ( scanf("%20s", buf) == 1 ){

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

4

6 回答 6

74

In their book The Practice of Programming (which is well worth reading), Kernighan and Pike discuss this problem, and they solve it by using snprintf() to create the string with the correct buffer size for passing to the scanf() family of functions. In effect:

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);
}

Note, this still limits the input to the size provided as 'buffer'. If you need more space, then you have to do memory allocation, or use a non-standard library function that does the memory allocation for you.


Note that the POSIX 2008 (2013) version of the scanf() family of functions supports a format modifier m (an assignment-allocation character) for string inputs (%s, %c, %[). Instead of taking a char * argument, it takes a char ** argument, and it allocates the necessary space for the value it reads:

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

If the sscanf() function fails to satisfy all the conversion specifications, then all the memory it allocated for %ms-like conversions is freed before the function returns.

于 2009-10-25T20:21:58.787 回答
33

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

int main()
{
  char *str = NULL;

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

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

于 2009-10-25T18:50:46.427 回答
7

大多数时候结合fgetssscanf完成工作。如果输入格式正确,另一件事是编写自己的解析器。另请注意,您的第二个示例需要进行一些修改才能安全使用:

#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)) { ...

就是这样。

于 2009-10-25T17:59:50.417 回答
4

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

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

于 2009-10-25T17:04:28.420 回答
1

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

但这是很多工作,所以大多数 C 程序员只是以任意长度截断输入。我想你已经知道了,但是使用 fgets() 不会让你接受任意数量的文本——你仍然需要设置一个限制。

于 2009-10-25T17:11:18.157 回答
0

制作一个为字符串分配所需内存的函数并不需要太多工作。那是我前段时间写的一个小 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;
}
于 2013-04-05T07:11:12.630 回答