来自 K&R 第二版标准库的 fgets 实现
fgets implementation from the std library of K&R 2nd edition
我知道教科书中的代码片段仅用于演示目的,不应遵守生产标准,但 K&R 第 2 版第 164-165 页说:
fgets
and fputs
, here they are, copied from the standard library
on our system:
char *fgets(char *s, int n, FILE *iop)
{
register int c;
register char *cs;
cs = s;
while (--n > 0 && (c = getc(iop)) != EOF)
if ((*cs++ = c) == '\n')
break;
*cs = '[=10=]';
return (c == EOF && cs == s) ? NULL : s;
}
为什么 return
语句不是 return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
因为:
If a read error occurs during the operation, the array contents are
indeterminate and a null pointer is returned.
- 即使是本书附录 B 中说明的标准库也是如此。来自第 247 页:
fgets
returns s
, or NULL
if end of file or error occurs.
- K&R 在
fputs
实施中使用 ferror(iop)
,在同一页面上的 fgets
实施下方给出。
通过上面的实现,fgets
将 return s
即使读取某些字符后出现读取 错误 。也许这是一个疏忽或者我错过了什么?
您是正确的,已发布的函数 fgets
实现的行为不符合 C89 标准。出于同样的原因,它也不符合现代C11/C18标准。
已发布的 fgets
实现正确处理 end-of-file,如果未读取单个字符,则仅 returning NULL
。但是,它不能正确处理流错误。根据您对 C89 标准的引用(在这方面与 C11/C18 标准相同),如果发生错误,该函数应始终 return NULL
,无论字符数如何读。因此,fgets
的已发布实现以与流错误完全相同的方式处理 end-of-file 是错误的。
值得注意的是K&R的第二版是1988年的,也就是ANSI C89标准发布之前的版本。因此,该标准的确切措辞可能在本书第二版编写时尚未最终确定。
fgets
的发布实现也不符合附录 B 中引用的规范。假设函数 fgets
的行为应该如附录 B 中指定的那样,那么发布的实现fgets
正确处理错误,但不能正确处理 end-of-file。根据附录 B 的引用,当出现 end-of-file 时,函数应该总是 return NULL
(即使已成功读取字符,这没有意义)。
还值得注意的是,使用语句
return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
问题中的建议不会使函数 fgets
的实现完全符合 C89/C11/C18 标准。当“在操作期间”发生流错误时,该函数应该 return NULL
。然而,当 ferror
returns 非零时,可能无法判断错误是否发生在“操作期间”,即在函数 fgets
被调用之前是否已经设置了流的错误指示符叫。由于调用 fgets
之前发生的错误,可能已经设置了流的错误指示器,但由于 end-of-file(即不是由于流错误),所有后续流操作成功或失败.函数 fgets
也不允许在函数开始时简单地调用 clearerr
来区分这些情况,因为它必须在 [=52] 之前恢复流错误指示器的状态=]ing。在 C 标准库中无法设置流的错误指示器;它需要一个 implementation-specific 函数。查看 getc
的 return 值并不总是能够解决这种歧义,因为 EOF
的 return 值可能同时表示 end-of-file 或错误。
Why is the return statement not return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
因为这也不是最好的选择。
return (c == EOF && cs == s) ? NULL : s;
很好地处理了由于 end-of-file 而导致的 EOF
的情况,但由于 输入错误 而无法处理 EOF
的情况。
ferror(iop)
刚刚发生输入错误时为真 或 之前发生输入错误时。由于输入错误,在 returning EOF
之后 fgetc()
可能 return 非 EOF
。这不同于 feof()
,后者是 sticky。一旦 EOF
由于 end-of-file 发生,feof()
继续 return EOF
,除非清除 end-of-file 标志。
更好的选择是确保 EOF
刚刚发生,而不是使用不合格的 ferror()
。
if (c == EOF) {
if (feof(iop)) {
if (cs == s) return NULL;
} else {
return NULL; // Input error just occurred.
}
}
return s;
迂腐n
病理病例:
当 n <= 0
as *cs = '[=28=]'
写入超出其合法范围的 cs[]
时,以下内容会受到影响。 --n
是 n == INT_MIN
时的问题。
while (--n > 0 && (c = getc(iop)) != EOF) // Is --n OK?
if ((*cs++ = c) == '\n')
break;
*cs = '[=11=]'; // Is n big enough
// Alternative
while (n > 1 && (c = getc(iop)) != EOF) {
n--;
*cs++ = c;
if (c == '\n') {
break;
}
}
if (n > 0) *cs = '[=11=]';
迂腐CHAR_MAX >= INT_MAX
注意:在少数机器上(一些旧图形处理器),returning EOF
可能是有效的 CHAR_MAX
。未提供替代代码。
未初始化c
测试 c == EOF
是 UB,因为不确定 c
曾经设置过小的 n
。
最好初始化C
:register int c = 0;
更多:
What are all the reasons fgetc()
might return EOF
?.
Is fgets() returning NULL with a short buffer compliant?.
我知道教科书中的代码片段仅用于演示目的,不应遵守生产标准,但 K&R 第 2 版第 164-165 页说:
fgets
andfputs
, here they are, copied from the standard library on our system:
char *fgets(char *s, int n, FILE *iop)
{
register int c;
register char *cs;
cs = s;
while (--n > 0 && (c = getc(iop)) != EOF)
if ((*cs++ = c) == '\n')
break;
*cs = '[=10=]';
return (c == EOF && cs == s) ? NULL : s;
}
为什么 return
语句不是 return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
因为:
If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.
- 即使是本书附录 B 中说明的标准库也是如此。来自第 247 页:
fgets
returnss
, orNULL
if end of file or error occurs.
- K&R 在
fputs
实施中使用ferror(iop)
,在同一页面上的fgets
实施下方给出。
通过上面的实现,fgets
将 return s
即使读取某些字符后出现读取 错误 。也许这是一个疏忽或者我错过了什么?
您是正确的,已发布的函数 fgets
实现的行为不符合 C89 标准。出于同样的原因,它也不符合现代C11/C18标准。
已发布的 fgets
实现正确处理 end-of-file,如果未读取单个字符,则仅 returning NULL
。但是,它不能正确处理流错误。根据您对 C89 标准的引用(在这方面与 C11/C18 标准相同),如果发生错误,该函数应始终 return NULL
,无论字符数如何读。因此,fgets
的已发布实现以与流错误完全相同的方式处理 end-of-file 是错误的。
值得注意的是K&R的第二版是1988年的,也就是ANSI C89标准发布之前的版本。因此,该标准的确切措辞可能在本书第二版编写时尚未最终确定。
fgets
的发布实现也不符合附录 B 中引用的规范。假设函数 fgets
的行为应该如附录 B 中指定的那样,那么发布的实现fgets
正确处理错误,但不能正确处理 end-of-file。根据附录 B 的引用,当出现 end-of-file 时,函数应该总是 return NULL
(即使已成功读取字符,这没有意义)。
还值得注意的是,使用语句
return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
问题中的建议不会使函数 fgets
的实现完全符合 C89/C11/C18 标准。当“在操作期间”发生流错误时,该函数应该 return NULL
。然而,当 ferror
returns 非零时,可能无法判断错误是否发生在“操作期间”,即在函数 fgets
被调用之前是否已经设置了流的错误指示符叫。由于调用 fgets
之前发生的错误,可能已经设置了流的错误指示器,但由于 end-of-file(即不是由于流错误),所有后续流操作成功或失败.函数 fgets
也不允许在函数开始时简单地调用 clearerr
来区分这些情况,因为它必须在 [=52] 之前恢复流错误指示器的状态=]ing。在 C 标准库中无法设置流的错误指示器;它需要一个 implementation-specific 函数。查看 getc
的 return 值并不总是能够解决这种歧义,因为 EOF
的 return 值可能同时表示 end-of-file 或错误。
Why is the return statement not
return (ferror(iop) || (c == EOF && cs == s)) ? NULL : s;
因为这也不是最好的选择。
return (c == EOF && cs == s) ? NULL : s;
很好地处理了由于 end-of-file 而导致的 EOF
的情况,但由于 输入错误 而无法处理 EOF
的情况。
ferror(iop)
刚刚发生输入错误时为真 或 之前发生输入错误时。由于输入错误,在 returning EOF
之后 fgetc()
可能 return 非 EOF
。这不同于 feof()
,后者是 sticky。一旦 EOF
由于 end-of-file 发生,feof()
继续 return EOF
,除非清除 end-of-file 标志。
更好的选择是确保 EOF
刚刚发生,而不是使用不合格的 ferror()
。
if (c == EOF) {
if (feof(iop)) {
if (cs == s) return NULL;
} else {
return NULL; // Input error just occurred.
}
}
return s;
迂腐n
病理病例:
当 n <= 0
as *cs = '[=28=]'
写入超出其合法范围的 cs[]
时,以下内容会受到影响。 --n
是 n == INT_MIN
时的问题。
while (--n > 0 && (c = getc(iop)) != EOF) // Is --n OK?
if ((*cs++ = c) == '\n')
break;
*cs = '[=11=]'; // Is n big enough
// Alternative
while (n > 1 && (c = getc(iop)) != EOF) {
n--;
*cs++ = c;
if (c == '\n') {
break;
}
}
if (n > 0) *cs = '[=11=]';
迂腐CHAR_MAX >= INT_MAX
注意:在少数机器上(一些旧图形处理器),returning EOF
可能是有效的 CHAR_MAX
。未提供替代代码。
未初始化c
测试 c == EOF
是 UB,因为不确定 c
曾经设置过小的 n
。
最好初始化C
:register int c = 0;
更多:
What are all the reasons fgetc()
might return EOF
?.
Is fgets() returning NULL with a short buffer compliant?.