You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

未调用fclose关闭文件时append追加模式为何出现逆序写入?

问题现象

在Ubuntu环境下运行如下C++代码时,遗漏fclose()调用会导致文件写入顺序和预期不符:

#include <fstream>

void append(std::string path, int i){
   FILE *out = fopen(&path[0], "a");
   fprintf(out, "%d\n", i);
   //fclose(out);
}

int main(){
   for(int i=0; i<3; i++) append("./text.txt",i);
   return 0;
}
  • 预期输出(追加模式按调用顺序写入):
0
1
2
  • 实际输出(未调用fclose时):
2
1
0

补上fclose(out)调用后写入顺序即可恢复正常。

产生机制

这个现象是C标准IO缓冲、进程退出流清理逻辑、O_APPEND打开语义三者共同作用的结果:

  1. C标准IO的默认缓冲规则
    对普通磁盘文件,fopen()返回的FILE*流默认采用块缓冲:调用fprintf()写入的内容不会立刻触发系统调用写入磁盘,而是先存放在C库为该流分配的用户态内存缓冲区中,只有缓冲区满、主动调用fflush()/fclose()、或进程退出执行流清理时,缓冲区中的内容才会被真正提交给内核写入文件。

  2. 泄漏的流不会立刻触发写入
    代码每次调用append()都会以追加模式独立打开同一个文件,拿到一个全新的FILE*流,写入对应数字后既不关闭也不冲刷缓冲区,直接返回导致流指针丢失,形成资源泄漏:

    • i=0时打开的流,缓冲区存着"0\n"
    • i=1时打开的流,缓冲区存着"1\n"
    • i=2时打开的流,缓冲区存着"2\n"
      三次fopen对应三个独立的流、三个独立的用户态缓冲区,在main函数返回前,没有任何一次写入真正进入内核。
  3. 进程退出时的流冲刷顺序
    进程从main返回后,glibc会执行退出清理逻辑,遍历所有仍处于打开状态的FILE*流,逐个冲刷缓冲区、关闭文件。在当前Ubuntu使用的glibc实现中,流按打开顺序逆序被冲刷:

    • 最先冲刷最后打开的、存放"2\n"的流:触发write()系统调用,由于文件以O_APPEND模式打开,write前会原子性定位到文件末尾(此时文件为空),写入"2\n"
    • 其次冲刷i=1时打开的、存放"1\n"的流:同样触发write,定位到当前文件末尾(已经有"2\n"),写入"1\n"
    • 最后冲刷i=0时打开的、存放"0\n"的流:write到文件末尾,写入"0\n"
      最终文件内容就呈现为逆序的2\n1\n0\n。
  4. 添加fclose后恢复正常的原因
    每次调用fclose()时,会立刻先冲刷当前流的缓冲区,触发write系统调用把内容写入磁盘。三次append按i=0、1、2的顺序执行,fclose的顺序也对应0、1、2,每次写入都追加到当时的文件末尾,自然得到预期的正序内容。

注意:这个逆序结果是glibc特定实现下的表现,不属于C/C++标准规定的行为,泄漏FILE*指针本身属于未定义行为,换不同C库、不同操作系统可能出现完全不同的结果,生产环境绝对不能遗漏文件关闭操作。

内容的提问来源于stack exchange,提问作者sicheng mao

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:36:20