我猜想,ungetc() 在 scanf("%d") 之后可能会失败,因为 scanf 可能会为第一个非数字字符隐式调用 ungetc()。
但是如果格式以 %c 结尾,这不会发生,对吧?
3 回答
如果您阅读scanf 的源代码(实际上是运行 的内部函数scanf
),您会看到说明%c
符将触发CT_CHAR
switch case 的执行,它不会调用ungetc
标准输入。所以你是对的。
这仅适用于 GNU libc,但我希望其他实现具有类似的行为。
其他答案对于 glibc 是正确的。不幸的是,从标准的角度来看,这是一个难以回答的问题。
POSIX 指定了fscanf(3)
and ungetc(3)
,但没有描述它们的交互。它对前者有这样的说法:
除非转换规范包含转换说明符,否则应从输入中读取项目
n
。输入项应定义为输入字节的最长序列(直到任何指定的最大字段宽度,可以根据转换说明符以字符或字节为单位测量),它是匹配序列的初始子序列。输入项之后的第一个字节(如果有)应保持未读状态。
整个文件中唯一提到的ungetc
与OP的问题无关。但是,它确实确认这fscanf
意味着返回通过ungetc
. 这没有严重的疑问,但从ungetc
未定义“读取操作”的含义,因此在标准中有一个特定的行来指向是有帮助的。
该标准没有具体说明“第一个字节 [...] 应保持未读”的含义。我将其解释为意味着 C 库必须表现得好像没有读取字符一样,即使任何实现都必须读取字符才能知道输入项何时结束。反过来,这意味着它不应该阻止使用ungetc
,无论格式字符串如何。
(虽然这个读数可能看起来很紧张,但 POSIX 肯定不会说“第一个字节 [...] 应该保持未读或被推送到输入流上,就像被 ungetc() 一样”,如果他们想要的话,这是他们会写的允许在通话ungetc
后失败fscanf
。)
幸运的是,ungetc
的规范足够宽松,这实际上可以工作。POSIX 允许多个字节的回送,甚至指定在这种情况下会发生什么:
推回的字节应由该流上的后续读取以它们推入的相反顺序返回。
[...]
应提供一个字节的回推。如果在同一流上调用 ungetc() 次数过多而没有对该流进行干预读取或文件定位操作,则操作可能会失败。
它也方便地没有指定实现必须始终提供相同数量的回推字节,只是允许它们在第一次之后失败。这意味着一个符合要求的实现可以简单地提供两个字节的ungetc
推回,只要fscanf
只使用一个。
理论上,我认为该标准实际上需要在任何调用或其姊妹函数 ungetc
之后可用。是一个读操作,读操作应该给你留下至少一个字节的回退。在实践中,这是对标准的非常微妙的解读,您应该期望实现在这一点上有所不同。fscanf
fscanf
最后,作为一个实际问题,我发现这个程序在我的系统上成功执行,它有一个 glibc 2.21 的 Ubuntu 变体:
#include <stdio.h>
int main(int argc, char **arv){
FILE *file = fopen("/tmp/file.txt", "w+");
if(file == NULL){
perror("fopen");
return 2;
}
if(fprintf(file, "123\n") == EOF){
printf("fprintf failed.");
}
if(fseek(file, 0, SEEK_SET) == -1){
perror("fseek");
return 1;
}
if(ungetc((int)'1', file) == EOF){
printf("First ungetc failed");
return 3;
}
int value;
if(fscanf(file, "%d", &value) == EOF){
printf("fscanf failed");
return 4;
}
if(ungetc((int)'2', file) == EOF){
printf("Second ungetc failed");
return 5;
}
return 0;
}
这并不能保证 glibc总是做正确的事情,但它显然在这种特定情况下做正确的事情。
当然,它并不仅仅取决于格式是否以 %c 结尾,因为在此之前的转换说明符可能会失败。