写点什么

C++ 学习 ---cstdio 的源码学习分析 06- 关闭文件函数 fclose

作者:桑榆
  • 2022 年 10 月 06 日
    广东
  • 本文字数:5892 字

    阅读完需:约 19 分钟

cstdio 中的文件访问函数

stdio.h 中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下 fclose 对应的源码实现。

  • fopen:打开文件

  • fclose:关闭文件

  • fflush:刷新文件流

  • freopen:重新打开文件流(不同的文件或访问模式)

  • setbuf:设置 stream buf

  • setvbuf:改变 stream buf

关闭文件函数 fclose

给定需要关闭的 FILE 指针,关闭成功返回 0,失败返回 EOF(-1)。

int fclose ( FILE * stream );
复制代码

函数入口分析

实际上是通过了内部函数_IO_new_fclose 实现的,接口没有变化,还是传入 FILE 指针,返回 int 状态

//glibc/include/stdio.h187 extern int _IO_new_fclose (FILE*);188 #   define fclose(fp) _IO_new_fclose (fp)
//glibc/libio/iofclose.c 32 int 33 _IO_new_fclose (FILE *fp) 34 {
复制代码

1.检查 FILE 指针的合法性

调用 CHECK_FILE 宏,如果是下面两种情况中的一种,则需要设置 errno 为 EINVAL,并返回 EOF:

  • FILE == NULL,即空指针

  • (FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC:

#define _IO_MAGIC 0xFBAD0000 /* Magic number */

#define _IO_MAGIC_MASK 0xFFFF0000

如果两者不相等,说明_glags 的高 16 位被篡改了,但是实际上高 16 位应该一直是一个 Magic number,这时判断 FILE 是不合法

 32 int 33 _IO_new_fclose (FILE *fp) 34 { 35   int status; 36  37   CHECK_FILE(fp, EOF);
//glibc/libio/libioP.h865 #ifdef IO_DEBUG866 # define CHECK_FILE(FILE, RET) do { \867 if ((FILE) == NULL \868 || ((FILE)->_flags & _IO_MAGIC_MASK) != _IO_MAGIC) \869 { \870 __set_errno (EINVAL); \871 return RET; \872 } \873 } while (0)874 #else875 # define CHECK_FILE(FILE, RET) do { } while (0)876 #endif
62 /* Magic number and bits for the _flags field. The magic number is 63 mostly vestigial, but preserved for compatibility. It occupies the 64 high 16 bits of _flags; the low 16 bits are actual flag bits. */ 65 66 #define _IO_MAGIC 0xFBAD0000 /* Magic number */ 67 #define _IO_MAGIC_MASK 0xFFFF0000
复制代码

2.处理混用新旧 FILE stream 的程序,检测后特殊处理

先调用_IO_vtable_offset 进行检测,即查看 fp->_vtable_offset(vtable 表的偏移),不为 0 说明是旧的 stream;

114 # define _IO_vtable_offset(THIS) (THIS)->_vtable_offset

 39 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)        40   /* We desperately try to help programs which are using streams in a 41      strange way and mix old and new functions.  Detect old streams 42      here.  */ 43   if (_IO_vtable_offset (fp) != 0) 44     return _IO_old_fclose (fp); 45 #endif
复制代码

调用_IO_old_fclose 关闭 stream,基本流程与_IO_new_fclose 一致,这里就不深入分析了,我们重点看_IO_new_fclose 的流程。

细心的朋友也会注意到,在_IO_old_fclose 中同样也会检测是否是新的 stream 对象,然后调用_IO_new_fclose 进行关闭,这就是 Glibc 库的兼容性,保证不管调用哪一种都能正常完成我们想要完成的工作。

//glibc/libio/oldiofclose.c 34 int 35 attribute_compat_text_section 36 _IO_old_fclose (FILE *fp) 37 { 38   int status;                                                 39  40   CHECK_FILE(fp, EOF); 41  42   /* We desperately try to help programs which are using streams in a 43      strange way and mix old and new functions.  Detect new streams 44      here.  */ 45   if (fp->_vtable_offset == 0) 46     return _IO_new_fclose (fp); 47  48   /* First unlink the stream.  */ 49   if (fp->_flags & _IO_IS_FILEBUF) 50     _IO_un_link ((struct _IO_FILE_plus *) fp); 51  52   _IO_acquire_lock (fp); 53   if (fp->_flags & _IO_IS_FILEBUF) 54     status = _IO_old_file_close_it (fp); 55   else 56     status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; 57   _IO_release_lock (fp); 58   _IO_FINISH (fp); 59   if (_IO_have_backup (fp)) 60     _IO_free_backup_area (fp); 61   _IO_deallocate_file (fp); 62   return status; 63 }
复制代码

3.unlink stream

还记得在我们 open 文件时,曾经将 stream link 到_IO_list_all 上

105 _IO_new_file_init_internal (struct _IO_FILE_plus *fp)

113 _IO_link_in (fp);

现在正好相反需要做 unlink 的操作

 47   /* First unlink the stream.  */ 48   if (fp->_flags & _IO_IS_FILEBUF) 49     _IO_un_link ((struct _IO_FILE_plus *) fp);
复制代码

首先检测当前的 fp 是否是_IO_LINKED 状态,是才进行 unlink,否则直接结束;

然后是初始化局部变量,然后加锁,进行同步;

检测_IO_list_all 的状态:

  • _IO_list_all == NULL,没有需要 unlink 的,进到下一步;

  • fp == _IO_list_all,说明_IO_list_all 正好指向当前的 fp,fp 位于链表头,那么只需要将_IO_list_all 赋值为链表的下一个位置;

  • 其它情况,即 fp 处于链表中间,那就要从当前_IO_list_all 的下一个位置开始遍历,直到找到当前的 f==(FILE)fp,或者*f 等于 NULL 循环结束没找到,找到之后将当前的_chain 赋值为 fp->file._chain,即链表指针跳过了 fp,即删除了 fp 节点

然后将_flags 状态置为~_IO_LINKED

最后解锁,完成同步工作。

  51 void  52 _IO_un_link (struct _IO_FILE_plus *fp)  53 {  54   if (fp->file._flags & _IO_LINKED)  55     {  56       FILE **f;  57 #ifdef _IO_MTSAFE_IO  58       _IO_cleanup_region_start_noarg (flush_cleanup);  59       _IO_lock_lock (list_all_lock);  60       run_fp = (FILE *) fp;  61       _IO_flockfile ((FILE *) fp);  62 #endif  63       if (_IO_list_all == NULL)  64     ;  65       else if (fp == _IO_list_all)  66     _IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;  67       else  68     for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)  69       if (*f == (FILE *) fp)  70         {  71           *f = fp->file._chain;  72           break;  73         }  74       fp->file._flags &= ~_IO_LINKED;  75 #ifdef _IO_MTSAFE_IO  76       _IO_funlockfile ((FILE *) fp);  77       run_fp = NULL;  78       _IO_lock_unlock (list_all_lock);  79       _IO_cleanup_region_end (0);  80 #endif  81     }  82 }
复制代码

4.关闭 FILE 流对象

首先获取 fp 对应的锁;

根据_flags 状态_IO_IS_FILEBUF 决定是否调用_IO_file_close_it,否则判断之前使用 fp 的过程中是否出现了_IO_ERR_SEEN,有的话将状态置为-1(EOF);

释放锁;

调用_IO_FINISH 清除 fp 的相关内容

135 /* The 'finish' function does any final cleaning up of an _IO_FILE object.

136 It does not delete (free) it, but does everything else to finalize it.

137 It matches the streambuf::~streambuf virtual destructor. */

138 typedef void (*_IO_finish_t) (FILE , int); / finalize */

139 #define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)

 51   _IO_acquire_lock (fp); 52   if (fp->_flags & _IO_IS_FILEBUF)                                                                                                                    53     status = _IO_file_close_it (fp); 54   else 55     status = fp->_flags & _IO_ERR_SEEN ? -1 : 0; 56   _IO_release_lock (fp); 57   _IO_FINISH (fp);
复制代码

_IO_file_close_it

符号映射到_IO_new_file_close_it,这里函数里做的操作与https://xie.infoq.cn/article/ebc2af6fa385c99400d2c9c2e相反,调用_IO_do_flush 得到 write_status,调用_IO_unsave_markers,调用_IO_SYSCLOSE 真正关闭 fp,释放相关的 buffer,然后将相关的变量置为初始状态。

1420 versioned_symbol (libc, _IO_new_file_close_it, _IO_file_close_it, GLIBC_2_1);
126 int 127 _IO_new_file_close_it (FILE *fp) 128 { 129 int write_status; 130 if (!_IO_file_is_open (fp)) 131 return EOF; 132 133 if ((fp->_flags & _IO_NO_WRITES) == 0 134 && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0) 135 write_status = _IO_do_flush (fp); 136 else 137 write_status = 0; 138 139 _IO_unsave_markers (fp); 140 141 int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0 142 ? _IO_SYSCLOSE (fp) : 0); 143 144 /* Free buffer. */ 145 if (fp->_mode > 0) 146 { 147 if (_IO_have_wbackup (fp)) 148 _IO_free_wbackup_area (fp); 149 _IO_wsetb (fp, NULL, NULL, 0); 150 _IO_wsetg (fp, NULL, NULL, NULL); 151 _IO_wsetp (fp, NULL, NULL); 152 } 153 _IO_setb (fp, NULL, NULL, 0); 154 _IO_setg (fp, NULL, NULL, NULL); 155 _IO_setp (fp, NULL, NULL); 157 _IO_un_link ((struct _IO_FILE_plus *) fp); 158 fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS; 159 fp->_fileno = -1; 160 fp->_offset = _IO_pos_BAD; 161 162 return close_status ? close_status : write_status; 163 }
复制代码

_IO_FINISH

通过宏传入 fp 和 0(dummy),该函数中也是做一些 flush 的操作(保证当前的写入操作,将缓冲的信息写回文件中),清除 fp 中之前分配的变量值,然后尝试关闭文件(如果不是_IO_DELETE_DONT_CLOSE 的情况)。

1421 versioned_symbol (libc, _IO_new_file_finish, _IO_file_finish, GLIBC_2_1);
166 void 167 _IO_new_file_finish (FILE *fp, int dummy) 168 { 169 if (_IO_file_is_open (fp)) 170 { 171 _IO_do_flush (fp); 172 if (!(fp->_flags & _IO_DELETE_DONT_CLOSE)) 173 _IO_SYSCLOSE (fp); 174 } 175 _IO_default_finish (fp, 0); 176 } 177 libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)
复制代码

5.针对宽字符做 free 相关变量的处理

 58   if (fp->_mode > 0) 59     { 60       /* This stream has a wide orientation.  This means we have to free 61      the conversion functions.  */ 62       struct _IO_codecvt *cc = fp->_codecvt; 63  64       __libc_lock_lock (__gconv_lock); 65       __gconv_release_step (cc->__cd_in.step); 66       __gconv_release_step (cc->__cd_out.step); 67       __libc_lock_unlock (__gconv_lock); 68     } 69   else
复制代码

6.普通字符情况检测是否有缓存,有则需要清空缓存区域

缓存信息判断即我们 FILE 对象中的_IO_save_base 指针

532 #define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)

 70     { 71       if (_IO_have_backup (fp)) 72     _IO_free_backup_area (fp); 73     }
复制代码

清空操作也是对相关的变量置空,无法再访问,注意这里有一种特殊情况,即当前我们正要清空缓存区域时,正在进行缓存。

534 #define _IO_in_backup(fp) ((fp)->_flags & _IO_IN_BACKUP)

那我们需要额外做一个交换的操作之后再将 save 和 backup 部分置空

 185 void  186 _IO_free_backup_area (FILE *fp) 187 { 188   if (_IO_in_backup (fp)) 189     _IO_switch_to_main_get_area (fp);  /* Just in case. */ 190   free (fp->_IO_save_base); 191   fp->_IO_save_base = NULL; 192   fp->_IO_save_end = NULL; 193   fp->_IO_backup_base = NULL; 194 }
复制代码

将当前的_IO_read 信息与_IO_save 信息交换,并将_IO_read_ptr 赋值为_IO_read_base(即当前的_IO_save_base),即 read 信息回退到 save 的部分,多读取出来的部分后面将会被清空。

 124 /* Switch current get area from backup buffer to (start of) main get area. */ 125    126 void 127 _IO_switch_to_main_get_area (FILE *fp)                                         128 {      129   char *tmp; 130   fp->_flags &= ~_IO_IN_BACKUP; 131   /* Swap _IO_read_end and _IO_save_end. */ 132   tmp = fp->_IO_read_end; 133   fp->_IO_read_end = fp->_IO_save_end; 134   fp->_IO_save_end= tmp; 135   /* Swap _IO_read_base and _IO_save_base. */ 136   tmp = fp->_IO_read_base;  137   fp->_IO_read_base = fp->_IO_save_base; 138   fp->_IO_save_base = tmp; 139   /* Set _IO_read_ptr. */ 140   fp->_IO_read_ptr = fp->_IO_read_base; 141 }
复制代码

7.释放 fp,并返回 status

调用_IO_deallocate_file 完成这项工作

 74   _IO_deallocate_file (fp); 75   return status; 76 }
复制代码

_IO_deallocate_file 中根据 fp 的状态,如果是标准输入输出,标准 err 输出,那就直接返回;

如果是之前的 file 类型(针对 Glibc2.0 的兼容),实际上还是判断标准输入输出,标准 err 输出;

最后一种情况就要释放掉 fp 指向的内存,注意,这块内存是我们在 fopen 时 malloc 出来的,此时需要换回去。

849 /* Deallocate a stream if it is heap-allocated.  Preallocated850    stdin/stdout/stderr streams are not deallocated. */851 static inline void852 _IO_deallocate_file (FILE *fp)853 {854   /* The current stream variables.  */855   if (fp == (FILE *) &_IO_2_1_stdin_ || fp == (FILE *) &_IO_2_1_stdout_856       || fp == (FILE *) &_IO_2_1_stderr_)857     return;858 #if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)859   if (_IO_legacy_file (fp))860     return;861 #endif862   free (fp);863 }
841 static inline bool842 _IO_legacy_file (FILE *fp) 843 { 844 return fp == (FILE *) &_IO_stdin_ || fp == (FILE *) &_IO_stdout_845 || fp == (FILE *) &_IO_stderr_;846 }
复制代码

总结

fclose 的操作通过内部函数_IO_new_fclose 实现,中间先 unlink stream,然后关闭 FILE 流对象(通过_IO_file_close_it),最后做 FILE 对象的状态置位和内存清空和释放,中间针对缓存/宽字符等情况都做了特殊处理。

发布于: 刚刚阅读数: 4
用户头像

桑榆

关注

北海虽赊,扶摇可接;东隅已逝,桑榆非晚! 2020.02.29 加入

Android手机厂商-相机软件系统工程师 爬山/徒步/Coding

评论

发布
暂无评论
C++学习---cstdio的源码学习分析06-关闭文件函数fclose_c++_桑榆_InfoQ写作社区