浅析C标准I/O库

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-->
    <!--CRLF-->
    <!--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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值