C和C++安全编码(原书第2版)
上QQ阅读APP看书,第一时间看更新

2.5.2 C99

严格符合C99的应用程序的两个选项是用fgets()或getchar()取代gets()。

C标准fgets()函数和gets()拥有相似的行为。fgets()函数接受两个额外的参数:期望读入的字符数和输入流。我们可以通过指定stdin作为输入流来使fgets()模拟gets()的行为。

例2.9的程序片段中使用fgets()函数从stdin中读取一行文本。

例2.9 使用fgets()从stdin中读取


01  char buf[LINE_MAX];
02  int ch;
03  char *p;
04
05  if (fgets(buf, sizeof(buf), stdin)) {
06    /* fgets 
成功,
扫描查找换行符 */
07    p = strchr(buf, '\n');
08    if (p) {
09      *p = '\0';
10    }
11    else {
12      /* 
未找到换行符, 
刷新stdin
到行尾 */
13      while (((ch = getchar()) != '\n')
14            && !feof(stdin)
15            && !ferror(stdin)
16      );
17    }
18  }
19  else {
20    /* fgets 
失败, 
处理错误 */
21  }

与gets()不同,fgets()函数保留换行符,这意味着不能将fgets()作为gets()的直接替代品。

当使用fgets()时,可能只读取了一行的部分数据,然而,可以确定用户的输入是否被截断了,因为那样的话,输入缓冲区内将不会包含一个换行符。

fgets()函数从流中最多读入比指定数量少1个的字符到一个数组中。如果遇到换行符或者EOF标志,则不会继续读取。在最后一个字符读入数组中后,一个空字符随即被写入缓冲区的结尾处。

使用fgets()可以安全地处理太长而不能在目标数组中存储的输入行,但出于性能方面的原因,不推荐使用它。如果指定的输入字符数超过目标缓冲区的长度,fgets()函数也可能导致缓冲区溢出。

在严格符合C99标准的应用程序中更换gets()函数的第二个方法是使用getchar()函数。getchar()函数返回stdin指向的输入流中的下一个字符。如果流在EOF处,则该流的EOF标记就会被设置,且getchar()返回EOF。如果发生读取错误,则该流的错误标记就会被设置,且getchar()返回EOF。例2.10中的程序片段使用getchar()函数从stdin中读取一行文本。

例2.10 使用getchar()从stdin中读取


01  char buf[BUFSIZ];
02  int ch;
03  int index = 0;
04  int chars_read = 0;
05
06  while (((ch = getchar()) != '\n')
07          && !feof(stdin)
08          && !ferror(stdin))
09  {
10    if (index < BUFSIZ-1) {
11      buf[index++] = (unsigned char)ch;
12    }
13    chars_read++;
14  } /*  while
循环结束 */
15  buf[index] = '\0';  /* 
空终结符 */
16  if (feof(stdin)) {
17    /* 
处理 EOF */
18  }
19  if (ferror(stdin)) {
20    /* 
处理错误 */
21  }
22  if (chars_read > index) {
23    /* 
处理截断 */
24  }

如果在循环结束时,,feof(stdin) ! = 0,则表示循环读取到文件末尾,而没有遇到一个换行符。如果在循环结束时,fferror(stdin) ! = 0,则表示循环在遇到一个换行符之前,发生了读取错误。如果在循环结束时,chars_read > index,则表示输入的字符串已被截断。《C安全编码标准》[Seacord 2008],“FIO 34-C.使用int捕捉字符IO函数的返回值”,在此解决方案中也适用。

如果写入到没有设定正确的边界的缓冲区中,那么使用getchar()函数读取一行仍然可能导致缓冲区溢出。

一次读取一个字符在控制行为方面提供了更多的灵活性,而无须额外的性能开销。下面的测试对于while循环通常是足够的:


while (( (ch = getchar()) ! = '\n') && ch ! = EOF ) 

参见《C安全编码标准》[Seacord 2008],“FIO 35-C.在sizeof(int)==sizeof(char)的情况下,用feof()和ferror()检测文件结束和文件错误”,这种情况下,必须用feof()和ferror()来代替上面的语句的写法。