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

我使用这个代码:

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

什么是防止可能的缓冲区溢出的最好方法,以便它可以传递随机长度的string?

我知道我可以通过调用例如限制inputstring:

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

但我更喜欢能够处理任何用户input。 或者不能这样做安全使用scanf,我应该使用fgets?

Kernighan和派克在他们的书“编程实践” (这是值得一读的)中讨论了这个问题,他们通过使用snprintf()创build了正确的缓冲区大小的string来传递给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); } 

请注意,这仍将input限制为“缓冲区”提供的大小。 如果你需要更多的空间,那么你必须做内存分配,或者使用一个非标准的库函数为你分配内存。


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

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

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

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

 int main() { char *str = NULL; scanf ("%as", &str); if (str) { printf("\"%s\"\n", str); free(str); } return 0; } 

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

大部分时间, fgetssscanf的组合完成这项工作。 另一件事是编写自己的parsing器,如果input格式良好。 还要注意你的第二个例子需要一些修改才能安全使用:

 #define LENGTH 42 #define str(x) # x #define xstr(x) str(x) /* ... */ int nc = scanf("%"xstr(LENGTH)"[^\n]%*[^\n]", array); 

上面放弃inputstream,但不包括换行符( \n )字符。 你将需要添加一个getchar()来消耗这个。 还要检查你是否到达了stream的末尾:

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

就是这个。

直接使用scanf(3)及其变种会带来一些问题。 通常,用户和非交互式用例是根据input行来定义的。 很less见到这样的情况,如果没有find足够的对象,更多的线将解决问题,但这是scanf的默认模式。 (如果用户不知道在第一行input号码,第二行和第三行可能不会帮助。)

至less如果你fgets(3)你知道你的程序将需要多lessinput行,并且你将不会有任何缓冲区溢出…

限制input的长度肯定比较容易。 您可以使用循环接受任意长度的input,每次只读一点,必要时为string重新分配空间。

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

做一个为你的string分配所需内存的函数并没有太多的工作。 这是我前段时间写的一个小函数,我总是用它来读取string。

它将返回读取string,或者如果发生内存错误NULL。 但请注意,您必须释放()您的string,并始终检查它的返回值。

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