何时/为什么使用fscanf()函数是个坏主意?

7
一个回答中,有这样一句有趣的话:“使用fscanf()函数几乎总是一个坏主意,因为它在失败时可能会使您的文件指针处于未知位置。我更喜欢使用fgets()逐行读入,然后使用sscanf()进行解析。”
请问在什么情况下/为什么使用fgets()sscanf()来读取某些文件会更好呢?
6个回答

14

想象一个有三行的文件:

   1
   2b
   c

使用fscanf()读取整数时,第一行的读取会正常进行,但是在第二行fscanf()只能读取到'b',不知道该怎么做才能读取到第三行。你需要一些机制来跳过垃圾输入,以便查看第三行。

如果你使用fgets()sscanf(),你就可以保证文件指针每次移动一行,这样会更容易处理。总的来说,你仍然应该检查整个字符串,以报告其中的任何异常字符。

我个人更喜欢后一种方法,虽然我不同意“使用fscanf()几乎总是一个坏主意”的说法......对于大多数事情来说,fscanf()都完全可以胜任。


1
请将gets()更改为fgets()。绝对不应该使用gets() - Wiz
可能是打错了 :) 感谢你发现了这个问题。 - Chris Arguin

5
当匹配字符文字时,这种情况就会出现。假设你有以下代码:
```python if char == 'a': # do something ```
在这里,`'a'`是一个字符文字。
int n = fscanf(fp, "%d,%d", &i1, &i2);

考虑两种可能的输入 "323,A424" 和 "323A424"。
在这两种情况下,fscanf() 都将返回 1,并且下一个读取的字符将是 'A'。无法确定逗号是否被匹配。
话虽如此,只有在找到错误的实际源头很重要时才会有影响。在只需要知道存在格式不正确的输入错误的情况下,fscanf() 实际上优于编写自定义解析代码。

2
有两个原因:
  • scanf() 可能会使 stdin 处于难以预测的状态,这会使错误恢复变得困难甚至不可能(这在 fscanf() 中不是很严重);
  • 整个 scanf() 系列函数都使用指针作为参数,但没有长度限制,因此它们可能会溢出缓冲区并更改与缓冲区后面无关的变量,导致看似随机的内存损坏错误,这对于经验较少的 C 程序员来说非常难以理解、找到和调试。

初学者的 C 程序员常常会对指针和“地址-of”运算符感到困惑,并且经常忘记需要添加 &,或者在不需要时添加它“以防万一”。这会导致“随机”的段错误,对他们来说很难找到。虽然这不是 scanf() 的问题,但值得注意。

23 年后,我仍然记得当我开始学习 C 编程并不知道如何识别和调试这些错误时,这是一个巨大的痛苦,而且(作为一个教了多年 C 的初学者的人)很难向一个还不理解指针和堆栈的新手解释这些错误。

任何向初学者 C 程序员推荐 scanf() 的人都应该受到无情的鞭打。

好吧,也许不是“无情”的,但绝对需要某种形式的鞭打 ;o)


“将指针作为参数,但没有长度限制” 这个说法是误导的:对于大多数类型而言,大小是固定的(%i%d%lf),因此不需要长度限制。唯一的例外是使用%s读取字符串。但即使如此,也可以通过在s之间添加数字来指定限制 :对于声明为“char s [100]”的字符串,可以使用 %99s - Rudy Matela

2
当fscanf()失败时,由于输入故障或匹配失败,文件指针(即下一个字节将被读取的文件中的位置)留在了一个与fscanf()成功时不同的位置。这通常在顺序文件读取中是不可取的。逐行读取可以使文件输入可预测,同时单行故障可以单独处理。请保留HTML标签。

1

基本上,没有办法告诉函数不要超出你为其分配的内存区域的边界。

出现了许多替代品,如fnscanf,它试图通过为读取器指定最大限制来修复这些函数,从而允许它不会溢出。


1
虽然缓冲区溢出是scanf()函数族的一个问题,但它们与此处所询问的问题无关。-1 - Sparr
1
你能详细说明为什么使用fgets()和sscanf()读取某些文件可能更好吗?我正在扩展他的问题。我拒绝你过于雄心勃勃的“-1”。 - cyberconte
1
我认为“详细说明原因”意味着你的答案应该基于已经提出的前提,即文件指针问题。如果他想要其他原因,他就不会链接到问题的来源,或者引用相关部分。 - Sparr
啊,我理解为其他原因是真正的“其他原因”,因为他已经在问题中解释了原因;可能对同一个问题有不同的理解。 - cyberconte
1
由于原始问题参考与使用fscanf()来读取整行有关,因此与fgets()的比较以及有关缓冲区的问题更为相关,而不仅仅是在失败匹配时文件指针所指向的位置的问题,尽管这是其他线程中引用的示例。 - RBerteig

1
几乎总是不推荐使用fscanf()函数,因为在失败时它可能会使您的文件指针处于未知位置。我更喜欢使用fgets()获取每一行,然后使用sscanf()进行解析。
您可以始终使用ftell()查找文件中的当前位置,然后根据需要决定下一步操作。基本上,如果您知道可以期望什么,那么可以放心使用fscanf()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接