我使用这个代码:
while ( scanf("%s", buf) == 1 ){
防止可能的缓冲区溢出以便可以传递随机长度的字符串的最佳方法是什么?
我知道我可以通过调用来限制输入字符串:
while ( scanf("%20s", buf) == 1 ){
但我希望能够处理用户输入的任何内容。 或者不能使用 scanf 安全地完成此操作,而我应该使用 fgets?
在他们的书《编程实践》(非常值得一读)中,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);
}
如果
sscanf()
函数无法满足所有转换规范,则在函数返回之前,它为类似
%ms
的转换分配的所有内存都会被释放。 a
说明符让 scanf() 为您分配内存来保存输入:
int main()
{
char *str = NULL;
scanf ("%as", &str);
if (str) {
printf("\"%s\"\n", str);
free(str);
}
return 0;
}
编辑: 正如 Jonathan 指出的,您应该查阅 scanf
手册页,因为说明符可能不同 (
%m
),并且您可能需要在编译时启用某些定义。fgets
和
sscanf
的组合即可完成这项工作。另一件事是编写自己的解析器,如果输入格式正确的话。另请注意,您的第二个示例需要进行一些修改才能安全使用:#define LENGTH 42
#define str(x) # x
#define xstr(x) str(x)
/* ... */
int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array);
上面丢弃输入流,但不包括换行符(
\n
)。您需要添加
getchar()
才能使用它。scanf(3)
及其变体会带来许多问题。通常,用户和非交互式用例是根据输入行来定义的。如果没有找到足够的对象,则很少会出现更多行可以解决问题的情况,但这是 scanf 的默认模式。 (如果用户不知道在第一行输入数字,第二行和第三行可能没有帮助。)
至少如果你
fgets(3)
你知道你的程序需要多少输入行,并且你不会有任何缓冲区溢出......
但这需要大量工作,因此大多数 C 程序员只是以任意长度截断输入。我想您已经知道这一点,但是使用 fgets() 不会允许您接受任意数量的文本 - 您仍然需要设置一个限制。
如果发生内存错误,它将返回读取的字符串NULL。 但请注意,您必须
free()
您的字符串并始终检查其返回值。
#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;
}
在 64 位处理器中,您可以拥有的最大数量是 2^64-1,在 32 位处理器中,您可以拥有的最大数量是 2^32-1。
另一个问题是,大多数读取函数(如果不是全部)都使用整数作为输入的大小,并且您可以使用的最大数字是 2^32-1。
换句话说,当我们谈论 scanf() 的大小时,fgets() 没有区别。如果使用不当,fgets() 也会导致缓冲区溢出。之所以没有对 fgets() 进行此类转换,是因为 fgets() 要求您使用输入的大小,而 scanf() 则不使用,但这并不意味着您不应该这样做。