1. 概述
标准I/O库由ISO C标准说明,其与POSIX定义的文件I/O具有明显区别。标准I/O库是围绕流进行的,而POSIX定义的文件I/O则是针对文件描述符的。
2. 缓冲区
标准I/O 会对用户的I/O操作进行缓冲,共分为全缓冲、行缓冲及不缓冲三种。
全缓冲:直到用户定义的缓冲区被填满,才会调用系统I/O函数。
行缓冲:在缓冲区(一般大小为1024)被填满或者遇到换行符'/n’时才调用系统I/O函数
无缓冲:没有缓冲区,立即调用系统I/O函数。
ISO C要求下列缓冲特征:
a. 当且仅当标准输入和标准输出并不涉及交互作用设备时,它们才是全缓存的。
b. 标准出错决不会是全缓冲的
但是,这并没有告诉我们如果标准输入和输出涉及交互作用设备时,它们是不带缓存的还是行缓存的,以及标准输出是不带缓存的,还是行缓存的。
很多系统默认使用下列类型的缓冲:
a. 标准出错是不带缓冲的。
b. 如若是涉及终端设备的其他流,则它们是行缓冲的;否则是全缓冲的。
需要注意的是,这里的缓冲区是指用户空间的缓冲区。我们常常说系统调用read、write、open、close是unbuffered I/O,但是,write的底层也可以分配内核缓冲区。
3. FILE结构体
标准I/O 通过fopen会返回一个指向FILE的指针,FILE结构内容如下:
1: typedef struct _IO_FILE FILE;<!--CRLF-->
2:
<!--CRLF-->
3: struct _IO_FILE {<!--CRLF-->
4: int _flags; /* High-order word is _IO_MAGIC; rest is flags. */<!--CRLF-->
5: #define _IO_file_flags _flags<!--CRLF-->
6:
<!--CRLF-->
7: /* The following pointers correspond to the C++ streambuf protocol. */<!--CRLF-->
8: /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */<!--CRLF-->
9: char* _IO_read_ptr; /* Current read pointer */<!--CRLF-->
10: char* _IO_read_end; /* End of get area. */<!--CRLF-->
11: char* _IO_read_base; /* Start of putback+get area. */<!--CRLF-->
12: char* _IO_write_base; /* Start of put area. */<!--CRLF-->
13: char* _IO_write_ptr; /* Current put pointer. */<!--CRLF-->
14: char* _IO_write_end; /* End of put area. */<!--CRLF-->
15: char* _IO_buf_base; /* Start of reserve area. */<!--CRLF-->
16: char* _IO_buf_end; /* End of reserve area. */<!--CRLF-->
17: /* The following fields are used to support backing up and undo. */<!--CRLF-->
18: char *_IO_save_base; /* Pointer to start of non-current get area. */<!--CRLF-->
19: char *_IO_backup_base; /* Pointer to first valid character of backup area */<!--CRLF-->
20: char *_IO_save_end; /* Pointer to end of non-current get area. */<!--CRLF-->
21:
<!--CRLF-->
22: struct _IO_marker *_markers;<!--CRLF-->
23:
<!--CRLF-->
24: struct _IO_FILE *_chain;<!--CRLF-->
25: int _fileno;<!--CRLF-->
26: #if 0<!--CRLF-->
27: int _blksize;<!--CRLF-->
28: #else<!--CRLF-->
29: int _flags2;<!--CRLF-->
30: #endif<!--CRLF-->
31: _IO_off_t _old_offset; /* This used to be _offset but it's too small. */<!--CRLF-->
32:
<!--CRLF-->
33: #define __HAVE_COLUMN /* temporary */<!--CRLF-->
34: /* 1+column number of pbase(); 0 is unknown. */<!--CRLF-->
35: unsigned short _cur_column;<!--CRLF-->
36: signed char _vtable_offset;<!--CRLF-->
37: char _shortbuf[1];<!--CRLF-->
38:
<!--CRLF-->
39: /* char* _save_gptr; char* _save_egptr; */<!--CRLF-->
40:
<!--CRLF-->
41: _IO_lock_t *_lock;
<!--CRLF-->
42: #ifdef _IO_USE_OLD_IO_FILE<!--CRLF-->
43: };
<!--CRLF-->
标准库函数都是通过对FILE的操作来实现读写文件的目的。
4. 标准输入、标准输出和标准出错
对进程,系统预定义了上述三个流,并且这三个流可以自动地被进程使用,这三个流通过预定义文件指针:stdin、stdout、stderr加以引用。
我们可以查看其缓冲区属性,程序如下:
1: //输出FILE属性<!--CRLF-->
2: void streaming_property(FILE* file)<!--CRLF-->
3: {
<!--CRLF-->
4: //输出FILE缓冲区类型<!--CRLF-->
5: if(file->_flags & _IO_UNBUFFERED)<!--CRLF-->
6: printf("unbuffered/n");<!--CRLF-->
7: else if(file->_flags & _IO_LINE_BUF)<!--CRLF-->
8: printf("line-buffered/n");<!--CRLF-->
9: else<!--CRLF-->
10: printf("fully-buffered/n");<!--CRLF-->
11:
<!--CRLF-->
12: //输出缓冲区大小<!--CRLF-->
13: printf("buffer size is %d/n", file->_IO_buf_end -<!--CRLF-->
14: file->_IO_buf_base);
<!--CRLF-->
15:
<!--CRLF-->
16: //输出discriptor值<!--CRLF-->
17: printf("discriptor is %d/n/n", fileno(file));<!--CRLF-->
18: }
<!--CRLF-->
19:
<!--CRLF-->
20:
<!--CRLF-->
21: int main(int argc, char** argv)<!--CRLF-->
22: {
<!--CRLF-->
23: printf("stdin is ");<!--CRLF-->
24: streaming_property(stdin);
<!--CRLF-->
25:
<!--CRLF-->
26: printf("stdout is ");<!--CRLF-->
27: streaming_property(stdout);
<!--CRLF-->
28:
<!--CRLF-->
29: printf("stderr is ");<!--CRLF-->
30: streaming_property(stderr);
<!--CRLF-->
31: }
<!--CRLF-->
输出:
1: stdin is fully-buffered<!--CRLF-->
2: buffer size is 0
<!--CRLF-->
3: discriptor is 0
<!--CRLF-->
4:
<!--CRLF-->
5: stdout is line-buffered<!--CRLF-->
6: buffer size is 1024
<!--CRLF-->
7: discriptor is 1
<!--CRLF-->
8:
<!--CRLF-->
9: stderr is unbuffered<!--CRLF-->
10: buffer size is 0
<!--CRLF-->
11: discriptor is 2
<!--CRLF-->
当然,我们也可以改变缓冲区属性,后面将在例子中介绍。
5. 常用的标准库函数
- 更改缓冲类型:setbuf/setvbuf
流的打开和关闭: fopen/fclose - 单字符输入: getc/fgetc/getchar
- 流的错误处理: ferror/feof/clearerr
- 返还字符: ungetc
- 单字符输出: putc/fputc/putchar
- 行输入: gets/fgets
- 行输出: puts/fputs
- 二进制读写: fwrite/fread
- 流的定位: ftell/fseek/rewind/fgetpos/fsetpos
- 格式化输出: printf/fprintf/sprintf
- 格式化输入: scanf/fscanf/sscanf
-
- 6. 示例
- A. 下面这个例子更改stdin缓冲区类为全缓冲,缓冲区大小为1024,输入一串字符,观察输出:
-
1: void streaming_property(FILE* file)
<!--CRLF-->2: {
<!--CRLF-->3:
<!--CRLF-->4: if(file->_flags & _IO_UNBUFFERED)
<!--CRLF-->5: printf("unbuffered/n");
<!--CRLF-->6: else if(file->_flags & _IO_LINE_BUF)
<!--CRLF-->7: printf("line-buffered/n");
<!--CRLF-->8: else
<!--CRLF-->9: printf("fully-buffered/n");
<!--CRLF-->10:
<!--CRLF-->11: printf("buffer size is %d/n", file->_IO_buf_end -
<!--CRLF-->12: file->_IO_buf_base);
<!--CRLF-->13:
<!--CRLF-->14: printf("discriptor is %d/n/n", fileno(file));
<!--CRLF-->15: }
<!--CRLF-->16:
<!--CRLF-->17:
<!--CRLF-->18: int main(int argc, char** argv)
<!--CRLF-->19: {
<!--CRLF-->20: printf("stdin is ");
<!--CRLF-->21: streaming_property(stdin);
<!--CRLF-->22:
<!--CRLF-->23: cout << "Change BUF" << endl << endl;
<!--CRLF-->24:
<!--CRLF-->25: //修改stdin为全缓冲区,缓冲区大小为1024
<!--CRLF-->26: char buf[1024];
<!--CRLF-->27: setvbuf(stdin, buf, _IOFBF, 1024);
<!--CRLF-->28:
<!--CRLF-->29: printf("stdin is ");
<!--CRLF-->30: streaming_property(stdin);
<!--CRLF-->31:
<!--CRLF-->32:
<!--CRLF-->33: char c ;
<!--CRLF-->34:
<!--CRLF-->35: int x = 0;
<!--CRLF-->36:
<!--CRLF-->37: //输入为q时,退出
<!--CRLF-->38: while((c = getc(stdin)) != 'q')
<!--CRLF-->39: {
<!--CRLF-->40:
<!--CRLF-->41: switch(c)
<!--CRLF-->42: {
<!--CRLF-->43: case 'A':
<!--CRLF-->44: x++;
<!--CRLF-->45: break;
<!--CRLF-->46: case 'B':
<!--CRLF-->47: cout << endl << "x = " << x << endl;;
<!--CRLF-->48: break;
<!--CRLF-->49: case 'C':
<!--CRLF-->50: x--;
<!--CRLF-->51: break;
<!--CRLF-->52: default:
<!--CRLF-->53: cout << "please input A/B/C/q" << endl;
<!--CRLF-->54: }
<!--CRLF-->55: }
<!--CRLF-->56:
<!--CRLF-->57: return 0;
<!--CRLF-->58: }
<!--CRLF-->59:
<!--CRLF-->60: 输出:
<!--CRLF-->61: $ ./bin/test
<!--CRLF-->62: stdin is fully-buffered
<!--CRLF-->63: buffer size is 0
<!--CRLF-->64: discriptor is 0
<!--CRLF-->65:
<!--CRLF-->66: Change BUF
<!--CRLF-->67:
<!--CRLF-->68: stdin is fully-buffered
<!--CRLF-->69: buffer size is 1024
<!--CRLF-->70: discriptor is 0
<!--CRLF-->71:
<!--CRLF-->72: AAAAAAAAAAAAAAAAAAAAABqqqqqqqqqqqqqqqqqqqqqq
<!--CRLF-->73:
<!--CRLF-->74: x = 21
<!--CRLF-->75: $
<!--CRLF-->
B. 下面这个例子更改stdin缓冲区类为无缓冲,输入一串字符,观察输出:
1: void streaming_property(FILE* file)<!--CRLF-->
2: {
<!--CRLF-->
3:
<!--CRLF-->
4: if(file->_flags & _IO_UNBUFFERED)<!--CRLF-->
5: printf("unbuffered/n");<!--CRLF-->
6: else if(file->_flags & _IO_LINE_BUF)<!--CRLF-->
7: printf("line-buffered/n");<!--CRLF-->
8: else<!--CRLF-->
9: printf("fully-buffered/n");<!--CRLF-->
10:
<!--CRLF-->
11: printf("buffer size is %d/n", file->_IO_buf_end -<!--CRLF-->
12: file->_IO_buf_base);
<!--CRLF-->
13:
<!--CRLF-->
14: printf("discriptor is %d/n/n", fileno(file));<!--CRLF-->
15: }
<!--CRLF-->
16:
<!--CRLF-->
17:
<!--CRLF-->
18: int main(int argc, char** argv)<!--CRLF-->
19: {
<!--CRLF-->
20: printf("stdin is ");<!--CRLF-->
21: streaming_property(stdin);
<!--CRLF-->
22:
<!--CRLF-->
23: cout << "Change BUF" << endl << endl;<!--CRLF-->
24:
<!--CRLF-->
25: //设置为无缓冲<!--CRLF-->
26: setvbuf(stdin, NULL, _IONBF, 0);<!--CRLF-->
27:
<!--CRLF-->
28: printf("stdin is ");<!--CRLF-->
29: streaming_property(stdin);
<!--CRLF-->
30:
<!--CRLF-->
31:
<!--CRLF-->
32: char c ;<!--CRLF-->
33:
<!--CRLF-->
34: int x = 0;<!--CRLF-->
35:
<!--CRLF-->
36:
<!--CRLF-->
37: while((c = getc(stdin)) != 'q')<!--CRLF-->
38: {
<!--CRLF-->
39:
<!--CRLF-->
40: switch(c)<!--CRLF-->
41: {
<!--CRLF-->
42: case 'A':<!--CRLF-->
43: x++;
<!--CRLF-->
44: break;<!--CRLF-->
45: case 'B':<!--CRLF-->
46: cout << endl << "x = " << x << endl;;<!--CRLF-->
47: break;<!--CRLF-->
48: case 'C':<!--CRLF-->
49: x--;
<!--CRLF-->
50: break;<!--CRLF-->
51: default:<!--CRLF-->
52: cout << "please input A/B/C/q" << endl;<!--CRLF-->
53: }
<!--CRLF-->
54: }
<!--CRLF-->
55:
<!--CRLF-->
56: return 0;<!--CRLF-->
57: }
<!--CRLF-->
58:
<!--CRLF-->
59: 输出:
<!--CRLF-->
60: $ ./bin/test
<!--CRLF-->
61: stdin is fully-buffered
<!--CRLF-->
62: buffer size is 0
<!--CRLF-->
63: discriptor is 0
<!--CRLF-->
64:
<!--CRLF-->
65: Change BUF
<!--CRLF-->
66:
<!--CRLF-->
67: stdin is unbuffered
<!--CRLF-->
68: buffer size is 1
<!--CRLF-->
69: discriptor is 0
<!--CRLF-->
70:
<!--CRLF-->
71: AAAAAAAAAAAAAAAAAAAQqqqqqqqqqqqqqqqqq
<!--CRLF-->
72: please input A/B/C/q<!--CRLF-->
73: $ qqqqqqqqqqqqqqqq<!--CRLF-->
74: qqqqqqqqqqqqqqqq: command not found<!--CRLF-->
分析:A设置缓冲区大小为1024,因此AAAAAAAAAAAAAAAAAAAAABqqqqqqqqqqqqqqqqqqqqqq,后面的q全部进入缓冲区,因此,getc取第一个,swich逻辑判断发现q则退出程序,缓冲区自动清空。
B设置无缓冲区,则第一个q之后的qqqqqqqqqqqqqqqq串及输入的回车仍然保留在终端,程序退出后,终端误认为是命令,输出’command not found’
7. 结语
标准I/O库功能强大,可移植性好,并且无需考虑缓存及最佳I / O长度的选择,并且它并不比直接调用read、write慢多少。一个效率方面的不足之处是需要复制的数据量:当使用每次一行函数fgets和fputs时,通常需要复制两次数据:一次是在内核和标准I / O缓存之间(当调用read和write时),第二次是在标准I / O缓存(通常系统分配和管理)和用户程序中的行缓存(fgets的参数就需要一个用户行缓存指针)之间。
参考文献
1. apue第二版
2. http://hi.chinaunix.net/?uid-20693307-action-viewspace-itemid-46724