FILE结构体
C语言的stdio.h头文件中,定义了用于文件操作的结构体FILE。这样,我们通过fopen返回一个文件指针(指向FILE结构体的指针)来进行文件操作。可以在stdio.h(位于visual studio安装目录下的include文件夹下)头文件中查看FILE结构体的定义,如下:
#ifndef _FILE_DEFINED
struct _iobuf {
charchar *_ptr; //文件输入的下一个位置
int _cnt; //当前缓冲区的相对位置
charchar *_base; //基础位置(文件的起始位置)
int _flag; //文件标志
int _file; //文件的有效性验证
int _charbuf; //检查缓冲区状况,如果无缓冲则不读取
int _bufsiz; //当前缓冲区有效大小
charchar *_tmpfname; //临时文件名
};
typedef struct _iobuf FILE;
#define _FILE_DEFINED
#endif /* _FILE_DEFINED */
FILE结构体包含文件描述符及缓冲区等
在这里我们只讨论文件描述符及缓冲区
1. 文件描述符
在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。而文件描述符是以宏的方式放在头文件中的。
(文件描述符的本质就是文件对象列表中数组的下标)
当打开一个新的文件时,分配列表内最小的一个描述符。
标准流对应的文件描述符如下表所示:
文件描述符 | 作用 | stdio流 |
---|---|---|
0 | 标准输入 | stdin |
1 | 标准输出 | stdout |
2 | 标准错误 | stderror |
原理图如下:
系统级打开文件表复制了文件控制块的信息等;进程级打开文件表保存了指向系统级文件表的指针及其他信息。
系统级文件表每一项都保存一个计数器,即该文件打开的次数。我们初次打开一个文件时,系统首先查看该文件是否已在系统级文件表中,如果不在,则创建该项信息,否则,计数器加1。当我们关闭一个文件时,相应的计数也会减1,当减到0时,系统将系统级文件表中的项删除。
进程打开一个文件时,会在进程级文件表中添加一项。每项的信息包括当前文件偏移量(读写文件的位置)、存取权限、和一个指向系统级文件表中对应文件项的指针。系统级文件表中的每一项通过文件描述符(一个非负整数)来标识。
系统只认文件描述符标志,因此在这里可以实现输入输出重定向。至于重定向的实现如下:
通过改变文件描述符(close(0 or 1 or 2 …))控制输入、输出位置。
这里重定向的代码在我的另一篇博客里有具体实现,在此不做赘述。
2. 缓冲区
我们知道,当我们从键盘输入数据的时候,数据并不是直接被我们得到,而是放在了缓冲区中,然后我们从缓冲区中得到我们想要的数据 。如果我们通过setbuf()或setvbuf()函数将缓冲区设置10个字节的大小,而我们从键盘输入了20个字节大小的数据,这样我们输入的前10个数据会放在缓冲区中,因为我们设置的缓冲区的大小只能够装下10个字节大小的数据,装不下20个字节大小的数据。那么剩下的那10个字节大小的数据怎么办呢?暂时放在了输入流中。
首先我们应该清楚缓冲方式通常有行缓冲、无缓冲、全缓冲三种,在往显示器内写入通常为行缓冲模式,再往文件内写入时通常用全缓冲,无缓冲暂且不加讨论。
下面我们来验证行缓冲与全缓冲:
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
const char *msg = "hello fwrite!\n";
const char *msg1 = "hello write\n";
printf("hello printf\n");
fwrite(msg, strlen(msg), 1, stdout);
write(1, msg, strlen(msg1));
if(fork()==0)//child
{
printf("child, pid: %d\n", getpid);
}
else
{
printf("father, pid: %d\n", getpid);
sleep(2);
}
return 0;
}
代码运行结果如下:
注意观察结果就不难发现因为缓冲区被刷新后的内容因为缓冲模式的不同而存在差异。其中fopen、fread…为库函数;open、read、write为系用调用。
若其中有不当之处,恳请留言指正