C标准(C11 n1570草案)指定
fgets()
的方式如下(一些强调是我的):
7.21.7.2 The fgets
function
Synopsis
#include <stdio.h>
char *fgets(char * restrict s, int n,
FILE * restrict stream);
Description
The fgets
function reads at most one less than the number of characters specified by n
from the stream pointed to by stream
into the array pointed to by s
. No additional characters are read after a new-line character (which is retained) or after end-of-file. A null character is written immediately after the last character read into the array.
Returns
The fgets
function returns s
if successful. If end-of-file is encountered and no characters have been read into the array, the contents of the array remain unchanged and a null pointer is returned. If a read error occurs during the operation, the array contents are indeterminate and a null pointer is returned.
短语“最多读取比n指定的字符数少一个字符”不够精确。负数不能表示“字符数”,但0表示“没有字符”。似乎不可能“最多读取-1个字符”,因此当n <= 0时,该情况未被标准定义,并且具有未定义的行为。
对于n = 1,fgets被指定为最多读取0个字符,除非流无效或处于错误状态,否则应成功。短语“在数组中读入最后一个字符后立即写入空字符”是模棱两可的,因为没有字符被读入数组,但将这种特殊情况解释为意味着s[0] = '\0';是有意义的。gets_s的规范提供了相同的阅读,具有相同的不精确性。同样,行为没有明确定义,因此它是未定义的。
snprintf
的规范更加精确,明确指定了n = 0
的情况,并附有有用的语义。不幸的是,对于fgets
,无法实现这样的语义:
7.21.6.5 The snprintf
function
Synopsis
#include <stdio.h>
int snprintf(char * restrict s, size_t n,
const char * restrict format, ...);
Description
The snprintf
function is equivalent to fprintf
, except that the output is written into an array (specified by argument s
) rather than to a stream. If n
is zero, nothing is written, and s
may be a null pointer. Otherwise, output characters beyond the n-1
st are discarded rather than being written to the array, and a null character is written at the end of the characters actually written into the array. If copying takes place between objects that overlap, the behavior is undefined.
get_s()
的规范还澄清了n = 0
的情况,并将其作为运行时约束违规:
K.3.5.4.1 The gets_s
function
Synopsis
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdio.h>
char *gets_s(char *s, rsize_t n);
Runtime-constraints
s
shall not be a null pointer. n
shall neither be equal to zero nor be greater than RSIZE_MAX
. A new-line character, end-of-file, or read error shall occur within reading n-1
characters from stdin.
If there is a runtime-constraint violation, s[0]
is set to the null character, and characters are read and discarded from stdin
until a new-line character is read, or end-of-file or a read error occurs.
Description
The gets_s
function reads at most one less than the number of characters specified by n
from the stream pointed to by stdin
, into the array pointed to by s
. No additional characters are read after a new-line character (which is discarded) or after end-of-file. The discarded new-line character does not count towards number of characters read. A null character is written immediately after the last character read into the array.
If end-of-file is encountered and no characters have been read into the array, or if a read error occurs during the operation, then s[0]
is set to the null character, and the other elements of s
take unspecified values.
Recommended practice
The fgets
function allows properly-written programs to safely process input lines too long to store in the result array. In general this requires that callers of fgets
pay attention to the presence or absence of a new-line character in the result array. Consider using fgets
(along with any needed processing based on new-line characters) instead of gets_s
.
Returns
The gets_s
function returns s
if successful. If there was a runtime-constraint violation, or if end-of-file is encountered and no characters have been read into the array, or if a read error occurs during the operation, then a null pointer is returned.
您正在测试的C库似乎对这种情况有漏洞,这已在后来的glibc版本中得到修复。返回
NULL
应意味着某种失败条件(与成功相反):文件结束或读取错误。其他情况,如无效流或未打开读取的流,或多或少被明确描述为未定义行为。
n = 0
和
n < 0
的情况未定义。返回
NULL
是一个明智的选择,但是在标准中澄清
fgets()
的描述要求
n > 0
与
gets_s
一样将会很有用。
请注意,
fgets
还存在另一个规范问题:
n
参数的类型应该是
size_t
而不是
int
,但是这个函数最初是由C作者指定的,当时
size_t
甚至还没有被发明,因此在第一个C标准(C89)中保持不变。然而,更改它被认为是不可接受的,因为他们试图标准化现有的用法:签名的更改将在C库之间创建不一致性,并破坏使用函数指针或未经保护的函数的良好编写的现有代码。
1 C标准在4. 符合性的第2段中指定,如果违反了约束或运行时约束之外出现的“必须”或“不得”的要求,则其行为未定义。在本国际标准中,通过“未定义行为”或省略任何明确的行为定义来表示未定义行为。这三者之间没有强调的区别;它们都描述“未定义的行为”。
(s,1)
返回errno:0 feof:0 ferror:0 retval:0x7fff183c87a0 s[0]:0
。(s,0)
返回errno:0 feof:0 ferror:0 retval:(nil) s[0]:42
。 - Oliver Matthewsfgets()
的签名是char *fgets(char * restrict s, int n, FILE * restrict stream);
(参见 POSIXfgets()
),其中大小使用的是int
而不是size_t
。这意味着n
可以是负数,但我不确定如果是负数会发生什么。由于没有空间来存放空字节,所以它不应该写入任何内容。返回 NULL 是合理的。POSIX 可以设置errno
,但它没有提到最适当的EINVAL
。 - Jonathan Leffler