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

129 阅读8分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第13篇文章,点击查看活动详情

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.h
187 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.h
865 #ifdef IO_DEBUG
866 # 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 #else
875 # 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,这里函数里做的操作与IO_new_file_fopen函数相反,调用_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.  Preallocated
850    stdin/stdout/stderr streams are not deallocated. */
851 static inline void
852 _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 #endif
862   free (fp);
863 }

841 static inline bool
842 _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对象的状态置位和内存清空和释放,中间针对缓存/宽字符等情况都做了特殊处理。