我用这个代码:
while ( scanf("%s", buf) == 1 ){
要防止可能的缓冲区溢出,以便可以传递随机长度的字符串,最好的方法是什么?我知道我可以通过调用以下示例来限制输入字符串:
while ( scanf("%20s", buf) == 1 ){
但是我更希望能够处理用户输入的任何内容,或者这不能用scanf安全地完成,我应该用fgets吗?
wfveoks01#
Kernighan和Pike在他们的书The Practice of Programming(非常值得一阅读)中讨论了这个问题,他们通过使用snprintf()创建一个具有正确缓冲区大小的字符串来传递给scanf()函数族,从而解决了这个问题。
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); }
注意,这仍然限制了输入的大小,如'buffer'所提供的。如果你需要更多的空间,那么你必须做内存分配,或使用一个非标准的库函数为你做内存分配。请注意,POSIX 2008(2013)版本的scanf()系列函数支持字符串输入(%s、%c、%[)的格式修饰符m(一个赋值分配字符)。它不采用char *参数,而是采用char **参数,并为它读取的值分配必要的空间:
%s
%c
%[
m
char *
char **
char *buffer = 0; if (sscanf(data, "%ms", &buffer) == 1) { printf("String is: <<%s>>\n", buffer); free(buffer); }
如果sscanf()函数无法满足所有转换规范,则在函数返回之前,将释放为类似%ms的转换分配的所有内存。
sscanf()
%ms
hfwmuf9z2#
如果您使用的是gcc,则可以使用GNU扩展a说明符让scanf()分配内存来保存输入:
a
int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; }
**编辑:**正如Jonathan指出的,您应该参考scanf手册页,因为说明符可能不同(%m),并且您可能需要在编译时启用某些定义。
scanf
%m
bzzcjhmw3#
大多数情况下,fgets和sscanf的组合可以完成这项工作。另一件事是编写自己的解析器,如果输入格式正确的话。还要注意,第二个示例需要做一些修改,以便安全使用:
fgets
sscanf
#define LENGTH 42 #define str(x) # x #define xstr(x) str(x) /* ... */ int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array);
上面的代码丢弃了输入流,直到但不包括换行符(\n)。您需要添加一个getchar()来使用它。
\n
getchar()
s4n0splo4#
直接使用scanf(3)及其变体会带来很多问题。通常,用户和非交互用例是根据输入行来定义的。很少会看到这样的情况:如果没有找到足够的对象,更多的行就可以解决问题,但这是scanf的默认模式。(如果用户不知道在第一行输入数字,则第二行和第三行可能也没有帮助。)至少如果你知道你的程序需要多少输入行,你就不会有任何缓冲区溢出...
scanf(3)
kwvwclae5#
限制输入的长度显然更容易,你可以通过使用循环来接受任意长度的输入,一次阅读一位,必要时为字符串重新分配空间......但这需要做大量的工作,所以大多数C程序员只是将输入截断为某个任意长度。我想你已经知道了这一点,但使用fgets()并不允许你接受任意数量的文本--你仍然需要设置一个限制。
czq61nw16#
创建一个函数为你的字符串分配所需的内存并不需要太多的工作,这是我以前写的一个小c函数,我总是用它来读入字符串。它将返回读取的字符串或如果内存错误发生NULL。但要注意,你必须free()你的字符串,并始终检查其返回值。
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; }
6条答案
按热度按时间wfveoks01#
Kernighan和Pike在他们的书The Practice of Programming(非常值得一阅读)中讨论了这个问题,他们通过使用
snprintf()
创建一个具有正确缓冲区大小的字符串来传递给scanf()
函数族,从而解决了这个问题。注意,这仍然限制了输入的大小,如'buffer'所提供的。如果你需要更多的空间,那么你必须做内存分配,或使用一个非标准的库函数为你做内存分配。
请注意,POSIX 2008(2013)版本的
scanf()
系列函数支持字符串输入(%s
、%c
、%[
)的格式修饰符m
(一个赋值分配字符)。它不采用char *
参数,而是采用char **
参数,并为它读取的值分配必要的空间:如果
sscanf()
函数无法满足所有转换规范,则在函数返回之前,将释放为类似%ms
的转换分配的所有内存。hfwmuf9z2#
如果您使用的是gcc,则可以使用GNU扩展
a
说明符让scanf()分配内存来保存输入:**编辑:**正如Jonathan指出的,您应该参考
scanf
手册页,因为说明符可能不同(%m
),并且您可能需要在编译时启用某些定义。bzzcjhmw3#
大多数情况下,
fgets
和sscanf
的组合可以完成这项工作。另一件事是编写自己的解析器,如果输入格式正确的话。还要注意,第二个示例需要做一些修改,以便安全使用:上面的代码丢弃了输入流,直到但不包括换行符(
\n
)。您需要添加一个getchar()
来使用它。s4n0splo4#
直接使用
scanf(3)
及其变体会带来很多问题。通常,用户和非交互用例是根据输入行来定义的。很少会看到这样的情况:如果没有找到足够的对象,更多的行就可以解决问题,但这是scanf的默认模式。(如果用户不知道在第一行输入数字,则第二行和第三行可能也没有帮助。)至少如果你知道你的程序需要多少输入行,你就不会有任何缓冲区溢出...
kwvwclae5#
限制输入的长度显然更容易,你可以通过使用循环来接受任意长度的输入,一次阅读一位,必要时为字符串重新分配空间......
但这需要做大量的工作,所以大多数C程序员只是将输入截断为某个任意长度。我想你已经知道了这一点,但使用fgets()并不允许你接受任意数量的文本--你仍然需要设置一个限制。
czq61nw16#
创建一个函数为你的字符串分配所需的内存并不需要太多的工作,这是我以前写的一个小c函数,我总是用它来读入字符串。
它将返回读取的字符串或如果内存错误发生NULL。但要注意,你必须
free()
你的字符串,并始终检查其返回值。