我使用以下代码:
while ( scanf("%s", buf) == 1 ){
防止缓冲区溢出以使其可以传递随机长度的字符串的最佳方法是什么?
我知道我可以通过调用例如来限制输入字符串:
while ( scanf("%20s", buf) == 1 ){
但是我更希望能够处理用户输入的任何内容。还是不能使用scanf安全地完成此操作,而我应该使用fgets?
Answers:
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)版本scanf()
系列的功能支持的格式修改m
字符串输入(分配分配字符)( ,%s
,)。%c
%[
它不带char *
参数,而是带char **
参数,并为其读取的值分配必要的空间:
char *buffer = 0;
if (sscanf(data, "%ms", &buffer) == 1)
{
printf("String is: <<%s>>\n", buffer);
free(buffer);
}
如果sscanf()
函数不能满足所有转换规范,则%ms
在函数返回之前,将释放为类似转换分配的所有内存。
snprintf
将一些数据写入字符串缓冲区,然后sscanf
从创建的字符串中读取数据。scanf
从stdin读取的内容究竟在哪里替换呢?
snprintf
它,但它并不是实际的format参数,这也很令人困惑。
data
,因此sscanf()
是适当的。如果要改为从标准输入中读取,请删除data
参数并scanf()
改为调用。至于format
在调用中成为格式字符串的变量的名称选择sscanf()
,您可以根据需要对它进行重命名,但是其名称并不准确。我不确定哪种选择是有意义的。会in_format
更清楚吗?我不打算在此代码中进行更改。您可能会在自己的代码中使用这个想法。
scanf()
尚未将macOS上的文档记录为support %ms
,尽管有用。
如果使用的是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
),并且在编译时可能需要启用某些定义。
%ms
。该符号%a
是的同义词%f
(在输出时,它请求十六进制浮点数据)。对于GNU手册页scanf()
说:如果_程序编译它不可用gcc -std=c99
或GCC -D_ISOC99_SOURCE(除非_GNU_SOURCE
也指定了),在这种情况下,a
被解释为说明符浮点数(见上文)._
在大多数情况下,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)) { ...
就是这样。
feof
代码放在更大的上下文中?我问,因为该功能经常被错误地使用。
array
需要是char array[LENGTH+1];
创建一个为字符串分配所需内存的函数不需要太多工作。那是我前一段时间写的一个C函数,我总是用它来读取字符串。
它将返回读取的字符串或如果发生内存错误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;
}
sizeof (char)
根据定义1
。您在这里不需要它。
strerror(3)
)或期望传入预分配的字符串(如(strerror_r(3)
-或scanf(3)
)...
buflen-1
—谢谢。然后,您必须担心未签名的下溢(包装到相当大的数字),因此需要进行if
测试。如果有人不注意传递0作为大小assert()
,我很想用一个替换它,或者在开发assert()
之前将if
其替换为一个,以备不时之需。我没有仔细查看文档中的%0s
含义sscanf()
-该测试可能会更好if (buflen < 2)
。