IO FILE 之fclose 详解
字数 1287 2025-08-05 08:19:22
IO FILE 之 fclose 详解
1. 概述
本教程详细分析 glibc 中 fclose 函数的实现原理,重点关注文件关闭流程和堆内存管理机制。fclose 函数主要完成以下工作:
- 将 FILE 结构体从
_IO_list_all链表中移除 - 关闭文件并释放缓冲区
- 释放 FILE 结构体内存
2. 核心函数分析
2.1 _IO_new_fclose 主函数
位于 /libio/iofclose.c 的核心函数:
int _IO_new_fclose (_IO_FILE *fp) {
int status;
// 1. 从链表中移除
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
// 2. 关闭文件和释放缓冲区
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
// 3. 最终确认
_IO_FINISH (fp);
// 4. 释放内存
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr) {
fp->_IO_file_flags = 0;
free(fp);
}
return status;
}
2.2 _IO_un_link - 链表移除
位于 /libio/genops.c,负责将 FILE 结构体从全局链表 _IO_list_all 中移除:
void _IO_un_link (struct _IO_FILE_plus *fp) {
if (fp->file._flags & _IO_LINKED) { // 检查标志位
if (_IO_list_all == NULL) // 空链表
;
else if (fp == _IO_list_all) { // 链表头节点
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
++_IO_list_all_stamp;
}
else { // 中间节点
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (_IO_FILE *) fp) {
*f = fp->file._chain;
++_IO_list_all_stamp;
break;
}
}
fp->file._flags &= ~_IO_LINKED; // 清除标志位
}
}
关键点:
_IO_LINKED标志位 (0x80) 表示结构体是否在链表中- 更新
_IO_list_all_stamp防止并发问题 - 修改
_flags清除链接状态
2.3 _IO_file_close_it - 文件关闭和缓冲区释放
位于 /libio/fileops.c,核心功能函数:
int _IO_new_file_close_it (_IO_FILE *fp) {
// 1. 刷新输出缓冲区
if ((fp->_flags & _IO_NO_WRITES) == 0 &&
(fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
// 2. 关闭文件描述符
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
// 3. 释放缓冲区
_IO_setb (fp, NULL, NULL, 0); // 释放并置空缓冲区
_IO_setg (fp, NULL, NULL, NULL); // 清空输入缓冲区指针
_IO_setp (fp, NULL, NULL); // 清空输出缓冲区指针
// 4. 确保从链表移除
_IO_un_link ((struct _IO_FILE_plus *) fp);
// 5. 设置关闭状态
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
return close_status ? close_status : write_status;
}
2.3.1 _IO_do_flush 缓冲区刷新
宏定义实现:
#define _IO_do_flush(_f) \
((_f)->_mode <= 0 \
? _IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base) \
: _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \
((_f)->_wide_data->_IO_write_ptr \
- (_f)->_wide_data->_IO_write_base)))
实际调用 _IO_do_write 执行系统调用写入数据。
2.3.2 _IO_SYSCLOSE 文件关闭
调用 vtable 中的 __close 函数:
int _IO_file_close (_IO_FILE *fp) {
return close_not_cancel (fp->_fileno);
}
#define close_not_cancel(fd) __close (fd) // 最终调用系统调用 close
2.3.3 _IO_setb 缓冲区释放
关键的内存释放点:
void _IO_setb (_IO_FILE *f, char *b, char *eb, int a) {
if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF))
free (f->_IO_buf_base); // 释放缓冲区内存
f->_IO_buf_base = b;
f->_IO_buf_end = eb;
if (a)
f->_flags &= ~_IO_USER_BUF;
else
f->_flags |= _IO_USER_BUF;
}
2.4 _IO_FINISH - 最终确认
调用 vtable 中的 __finish 函数:
void _IO_new_file_finish (_IO_FILE *fp, int dummy) {
if (_IO_file_is_open (fp)) {
_IO_do_flush (fp);
if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
_IO_SYSCLOSE (fp);
}
_IO_default_finish (fp, 0);
}
_IO_default_finish 再次确认释放:
void _IO_default_finish (_IO_FILE *fp, int dummy) {
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) {
free (fp->_IO_buf_base);
fp->_IO_buf_base = fp->_IO_buf_end = NULL;
}
// 处理标记和保存缓冲区
if (fp->_IO_save_base) {
free (fp->_IO_save_base);
fp->_IO_save_base = NULL;
}
_IO_un_link ((struct _IO_FILE_plus *) fp);
}
3. 关键数据结构变化
3.1 _IO_list_all 链表变化
关闭前:
_IO_list_all -> FILE1 -> FILE2 -> ... -> 当前FILE -> ...
关闭后:
_IO_list_all -> FILE1 -> FILE2 -> ... (当前FILE已移除)
3.2 FILE 结构体标志位变化
_flags: 清除_IO_LINKED标志_fileno: 设置为 -1_IO_buf_base/_IO_buf_end: 设置为 NULL
3.3 堆内存释放顺序
- 输出缓冲区内存 (通过
_IO_setb) - 输入缓冲区内存 (如果有)
- FILE 结构体内存 (最后释放)
4. 安全注意事项
- 双重释放风险:
_IO_setb和_IO_default_finish都会检查并释放缓冲区,但通过_IO_USER_BUF标志避免 - 链表操作原子性:
_IO_list_all_stamp用于检测并发修改 - 标准流特殊处理:不释放 stdin/stdout/stderr 的 FILE 结构体
5. 调用流程图
fclose()
|
├─ _IO_un_link() // 从链表移除
│
├─ _IO_file_close_it() // 主要关闭逻辑
│ ├─ _IO_do_flush() // 刷新缓冲区
│ ├─ _IO_SYSCLOSE() // 关闭文件描述符
│ └─ _IO_setb() // 释放缓冲区
│
├─ _IO_FINISH() // 最终确认
│ └─ _IO_default_finish()
│
└─ free() // 释放FILE结构体
6. 总结
fclose 函数的完整执行流程体现了 glibc IO 系统的内存管理和资源清理机制,主要特点包括:
- 严格的链表管理确保全局状态一致
- 多重检查确保资源完全释放
- 通过标志位 (
_IO_LINKED,_IO_USER_BUF等) 控制流程 - 对标准流的特殊处理
- 缓冲区释放先于 FILE 结构体释放的安全顺序
理解这些机制对于分析 IO 相关的内存问题和安全漏洞至关重要。