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.c32 int33 _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 int33 _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 #endif62 /* 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.c34 int35 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 else56 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 void52 _IO_un_link (struct _IO_FILE_plus *fp)
53 {
54 if (fp->file._flags & _IO_LINKED)
55 {
56 FILE **f;
57 #ifdef _IO_MTSAFE_IO58 _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 #endif63 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 else68 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_IO76 _IO_funlockfile ((FILE *) fp);
77 run_fp = NULL;
78 _IO_lock_unlock (list_all_lock);
79 _IO_cleanup_region_end (0);
80 #endif81 }
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 else55 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 int127 _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) == 0134 && (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
135 write_status = _IO_do_flush (fp);
136 else137 write_status = 0;
138
139 _IO_unsave_markers (fp);
140
141 int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0142 ? _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 void167 _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 void127 _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对象的状态置位和内存清空和释放,中间针对缓存/宽字符等情况都做了特殊处理。