专栏文章

成事不足,败事有余

算法·理论参与者 19已保存评论 20

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
20 条
当前快照
2 份
快照标识符
@mibys7k0
此快照首次捕获于
2025/11/24 01:00
3 个月前
此快照最后确认于
2025/12/01 22:07
3 个月前
查看原文
声明
作者其实也只是一知半解。因此本文中的内容仅供参考,请勿将其作为严谨的学术内容看待。欢迎指出文中的错误,这对所有人都是好事。

引入

在 NOI 系列比赛中,文件 I/O 是必须学会的东西。
CPP
freopen("XXX.in","r",stdin);
freopen("XXX.out","w",stdout);
如果你不会写这个,并且不会别的文件 I/O 方式,想必你一定能取得一个“好成绩”。
而众所周知,cin/cout的速度非常慢,有时候可能会带来美妙的 TLE。所以,我们经常会解绑cin/cout,并且关闭 C/C++ 风格 I/O 的同步流,以提升 I/O 效率。
CPP
ios::sync_with_stdio(false);
cin.tie(nullptr);
最后,为了保险起见,有人还会加上最后两句。
CPP
fclose(stdin);
fclose(stdout);
那么想必这样的代码一定能取得好成绩吧!

转瞬即逝

是的,你忐忑地点开成绩查询页面,一看到成绩,悬着的心终于死了。 00 你随即打算以 9.8m/s29.8\text{m/s}^2 的加速度去楼下买辣条。不过在你做出这个举动之前,你想到测测自己的程序,于是你把它提交到某国内著名 OJ 上一测——这不 AC 了吗?
这里面似乎藏着更多。我们应该看看,究竟是什么导致了爆零的悲剧。

Hello,World

我们从最简单的程序——Hello,World 开始。
程序代码
一个很正常的程序。猜猜输出文件里有什么?
输出内容
什么都没有。
没错,输出文件里居然什么都没有!我们可以写几个不同的程序,但最后似乎总是没有输出。
爆零的原因已经知道了,就是输出的问题。那么,是什么造成了这种情况呢?显然要么是解绑和关同步流的问题,要么是fclose()的问题,毕竟你也不能怀疑到freopen()头上。我们试着把解绑的代码注掉重新运行,发现依旧没有输出。但当我们把关同步流或者fclose()中的任意一个注释掉,输出就成功了!
究竟是怎么一回事呢?

缓冲区

C++/C 流的缓冲区关系取决于是否关闭了同步流。
缓冲区是啥?它是内存中的一块临时存储区域,通过在数据传输过程中存储一定信息,减少调用效率低下的设备的次数,来优化运行速度。
举个例子,假如你是一个快递员,如果你每接到一个包裹就去送,来来往往的,非常浪费时间。于是你选择把包裹先送到快递站,等到快递站塞满了或者遇到特殊情况再去送,效率就高了不少。把全站的包裹全部拿去送的操作,我们称之为“刷新”。
默认情况下,C++/C 流是用同一个缓冲区的,这样就不会在混用时让顺序错乱。但这样做速度跟不上,于是 OIer 的大手发力了!他们关闭了同步流,现在 C++ 流拥有了独立的缓冲区。这下 C++ 流的速度就更快了,当然这也意味着不能混用它们了。
不过就算关闭了同步,C++/C 流仍然共用同一个文件描述符最终完成输出。

悲剧复盘

接下来我们来复盘悲剧是如何发生的:
  1. 通过freopen()重定向到文件;
  2. 关闭同步流,于是写入Hello,World到 C++ 流的缓冲区;
  3. 通过fclose()刷新 C 流的缓冲区,并关闭文件描述符
  4. 程序结束,C++ 流的缓冲区刷新,但由于文件描述符已经关闭,它的输出失败,缓冲区里的东西也就被直接丢弃了。
这就是为什么没有输出。如果没有关闭文件描述符这一步,C++ 流就能成功刷新并输出。
至于没有关闭同步流的情况,这是由于在同步流开启的情况下,C++/C 流共享一个缓冲区,在fclose()这一步后就成功输出了。要是关闭了同步流,C++ 流用上了独立的缓冲区,fclose()就管不到它了,并且由于fclose()关闭了文件描述符,之后的刷新就无能为力了。
成事不足,败事有余。

解决方案

很简单,不要手滑写fclose()。当然,如果你改不掉这个习惯,你应该在fclose()之前进行手动刷新操作。
CPP
cout.flush();
cout<<fulsh;
cout<<endl;
这些都是可以的,它们会刷新 C++ 流的缓冲区。不过最好还是不要写fclose()

后记

现在你终于知道了为什么会爆零!你斗志昂扬,决定下次一雪前耻。
不过还是先去买辣条吧。

评论

20 条评论,欢迎与作者交流。

正在加载评论...