首页
社区
课程
招聘
[原创]C语言的文件与缓冲区
发表于: 2024-9-1 21:31 1894

[原创]C语言的文件与缓冲区

2024-9-1 21:31
1894

FILE类型

在C语言中,有三个预定义的文件流:stdin,stdout,stderr,在VC++6的stdio.h中,它们的定义如下:

#define stdin  (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])

分别取自_iob数组下标为0、1、2的元素地址。

_iob的定义是一个FILE类型的数组:

/*
 * FILE descriptors; preset for stdin/out/err (note that the __tmpnum field
 * is not initialized)
 */
FILE _iob[_IOB_ENTRIES] = {
        /* _ptr, _cnt, _base,  _flag, _file, _charbuf, _bufsiz */

        /* stdin (_iob[0]) */

        { _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ },

        /* stdout (_iob[1]) */

        { NULL, 0, NULL, _IOWRT, 1, 0, 0 },

        /* stderr (_iob[3]) */

        { NULL, 0, NULL, _IOWRT, 2, 0, 0 },

};

FILE本质上是结构体_iobuf的类型别名:

struct _iobuf {
        char *_ptr;        // 文件指针
        int   _cnt;      // 缓冲区剩余大小
        char *_base;       // 缓冲区起始位置
        int   _flag;     // 标志/状态
        int   _file;     // 文件唯一标识(_iob数组下标)
        int   _charbuf;
        int   _bufsiz;   // 缓冲区大小
        char *_tmpfname;
        };
typedef struct _iobuf FILE;

结构体_iobuf中的_ptr,_base,_cnt,_bufsiz是与文件缓冲区相关的字段。

在使用fopen函数打开一个文件后,就会从_iob数组中选择一个空闲的元素分配给新的FILE。


源码调试

先准备一段C代码

#include <stdio.h>

int main(int argc, char *argv[], char *env[])
{
	FILE *fp = NULL;
	fp = fopen("c:/test.txt", "r+");
	if (fp == NULL)
	{
		fp = fopen("c:/test.txt", "w+");
		if (fp == NULL)
		{
			puts("Open file error");
			goto EXIT_PROC;
		}
	}

	fprintf(fp, "hello: %s\n", argv[0]);
	fflush(fp);

EXIT_PROC:
	if (fp != NULL)
	{
		fclose(fp);
		fp = NULL;
	}

	
    return 0;
}


将断点打在fopen处:

逐层步入:


一直到这里调用了系统函数CreateFile:


待fopen返回之后,fp便分配了值,而该值恰好是_iob数组下标为3的元素地址,0、1、2已经被预先占用了,所以新打开的文件从3开始。


在监视窗口中展开fp的内容可见,缓冲区相关的字段都为0,说明文件刚打开时,缓冲区尚未分配。

_flag = 0x80,转换为二进制是10000000,其中一个bit位置1,表示_iob数组中该元素已被占用。

_file = 3,代表文件标识,也是它在_iob数组中的下标。


文件缓冲区只有在文件真正被读写时才会分配,比如执行完fprintf后:

_base = 0x00382a80,代表文件缓冲区的起始地址;

_ptr是文件指针,指示当前写入到缓冲区的哪个位置;

_bufsiz = 0x1000,说明缓冲区大小是4096字节;

_cnt = 0x0fb1,说明缓冲区还剩余4017字节,也就是说刚刚写入了4096-4017=79字节数据;

_flag=0x8a,二进制为10001010,新增了两个置1的bit位。


在内存视图中,跳转到_base指向的内存区域,发现下面有大量0xCDCD填充的字节,很明显这是一块堆内存(可以参考我的另一篇文章:[原创]VC++6调试状态下的堆结构),堆块起始地址在向前偏移32字节处也就是0x00382a60,根据堆块的附加信息可知:blockType = 2也就是_CRT_BLOCK,是C运行时库所使用的类型。


注意,此时仅仅是将数据写入了内存缓冲区,实际磁盘文件中并没有内容:


如果想要将缓冲区的内容写入到磁盘,可以等待缓冲区满后自动触发存盘,也可以手动调用fflush手动同步,步入到fflush函数内部:


这里调用了WriteFile系统函数:

WriteFile执行后,磁盘中的文件便有了内容:


fflush函数返回后,缓冲区相关字段也发生了变化:

_ptr回到与_base相同的位置,_cnt置为0,此时在逻辑上表示了缓冲区中无内容的状态,但堆上的残留值并未清空;

_flag = 0x88,二进制为10001000,减少了一个置1的bit位。


接下来进入fclose函数内部:

从注释中可以得知,fclose做了4件事:

  1. 刷新文件流缓存

  2. 释放缓冲区

  3. 关闭文件

  4. 删除临时文件

这里调用_flush和fflush函数内部一样,会将缓冲区中的剩余数据写入磁盘,接下来调用_freebuf:

_freebuf执行完后,发现缓冲区已经变为0xFEEE填充的字节,说明堆块已经被释放;

_ptr 和 _base 都置为0;

_flag = 0x80,二进制为10000000,又减少了一个置1的bit位;


接下来步入_close函数,这里调用了系统函数CloseHandle用于关闭文件,fh的值为3,也就是文件所在_iob数组的下标。

在CloseHandle执行前,试图删除文件会失败,因为此时文件尚未关闭:

待CloseHandle执行过后,文件已关闭,即可正常删除:



在fclose函数末尾,有一个将_flag清0的操作,表示_iob数组中该元素已被释放,下次再打开新文件可以使用该元素了。


总结

  1. 打开文件相当于在_iob数组中占用了一个元素;

  2. 标准库函数fopen调用了系统函数CreateFile;

  3. 文件读写不会直接操作磁盘,而是操作缓冲区;

  4. fflush会调用WriteFile将缓冲区中的内容写入磁盘,同时将缓冲区逻辑上清空;

  5. 标准库函数fclose调用了系统函数CloseHandle。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//