zoukankan      html  css  js  c++  java
  • 刨根问底系列(2)——stdin、stdout、FILE结构体、缓冲区和fflush的理解

    stdin、stdout、FILE结构体、缓冲区和fflush理解

    因为之前调试代码时, printf输出的字符串总是被截断了输出(先输出部分, 再输出剩余的), 当时调试了很久, 才知道问题所在, 并用fflush函数解决了上述bug.

    1. stdin和stdout是什么

    它们是FILE*类型的结构体指针(所以并不是int类型的0,1,2), 只是程序默认一般打开的.

    man pages3中的定义:

    #include <stdio.h>
    
    extern FILE *stdin;
    extern FILE *stdout;
    extern FILE *stderr;
    

    /usr/include/x86_64-linux-gun/bits/types/FILE.h:

    /* The opaque type of streams.  This is the definition used elsewhere.  */
    typedef struct _IO_FILE FILE;
    

    stdio.h:

    extern struct _IO_FILE *stdin;      /* Standard input stream.  */
    extern struct _IO_FILE *stdout;     /* Standard output stream.  */
    extern struct _IO_FILE *stderr;     /* Standard error output stream.  */
    

    所以stdin等都是_IO_FILE类型结构体指针(也就是FILE*).

    2. FILE结构体与文件描述符的关系

    2.1 FILE结构体

    以下是Ubuntu18.04.4 server版中找到的源码.

    /usr/include/x86_64-linux-gun/bits/libio.h:

    struct _IO_FILE {                                                                                                                               
      int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
    #define _IO_file_flags _flags
    
      /* The following pointers correspond to the C++ streambuf protocol. */
      /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
      char* _IO_read_ptr;   /* Current read pointer */
      char* _IO_read_end;   /* End of get area. */
      char* _IO_read_base;  /* Start of putback+get area. */
      char* _IO_write_base; /* Start of put area. */
      char* _IO_write_ptr;  /* Current put pointer. */
      char* _IO_write_end;  /* End of put area. */
      char* _IO_buf_base;   /* Start of reserve area. */
      char* _IO_buf_end;    /* End of reserve area. */
      /* The following fields are used to support backing up and undo. */
      char *_IO_save_base; /* Pointer to start of non-current get area. */
      char *_IO_backup_base;  /* Pointer to first valid character of backup area */
      char *_IO_save_end; /* Pointer to end of non-current get area. */
    
      struct _IO_marker *_markers;
    
      struct _IO_FILE *_chain;
    
      int _fileno; //这个就是文件描述符
    #if 0
      int _blksize;
    #else
      int _flags2;
    #endif
      _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
    
    #define __HAVE_COLUMN /* temporary */
      /* 1+column number of pbase(); 0 is unknown. */
      unsigned short _cur_column;
      signed char _vtable_offset;
      char _shortbuf[1];
    
      /*  char* _save_gptr;  char* _save_egptr; */
    
      _IO_lock_t *_lock;
    #ifdef _IO_USE_OLD_IO_FILE
    };
    
    

    2.2 FILE结构体中的文件描述符

    其中最重要的是int _fileno, 这个就是文件描述符fd. 这个在源码注释中没有说明, 所以我就在找fd是哪个成员, 因为FILE肯定和文件描述符有关, 结构体中fileno比较像, 又在msdn fileno函数介绍ibm fileno函数介绍中发现fileno函数:

    // Gets the file descriptor associated with a stream.
    
    int _fileno(
       FILE *stream
    );
    

    所以编写以下测试了, 确实应该就是文件描述符了.

    #include <stdio.h>                                                              
    int main() { 
        printf("stdin->fileno = %d
    ", stdin->_fileno);
        printf("stdout->fileno = %d
    ", stdout->_fileno);
        printf("stderr->fileno = %d
    ", stderr->_fileno);
        return 0;
    } 
    //output
    stdin->fileno = 0
    stdout->fileno = 1
    stderr->fileno = 2
    

    2.3 FILE和文件描述符的关系

    先说结论: FILE是文件描述符的一种封装, 它可以完成更多的功能, 但是本质都会对文件描述符进行操作.

    为了更好地说明, 这里先以write函数和fwrite函数为例介绍:

    #include <unistd.h>
    ssize_t write(int fd, const void *buf, size_t count); 
    
    #include <stdio.h>
    size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
    
    
    1. write:

      • 系统调用, 通过unistd.h头也可以知道, 因为unistd.h 中所定义的接口通常都是大量针对类Unix系统调用的封装
      • 参数是文件描述符
    2. fwrite:

      • c标准库函数, 通过stdio.h也可以知道, 所以fwrite最终是要通过write这一系统调用来实现的.
      • 参数是FILE(FILE中有文件描述符的成员变量)

    从这就可以看出, FILE封装fd, 其实也是为了更方便的对文件进行操作, 但是本质都会通过系统调用对fd进行操作.

    下一节就介绍提供的功能之缓冲区.

    3. FILE的缓冲区功能和fflush函数

    3.1 FILE的缓冲区功能

    以下内容参考csdn FILE缓存区的介绍.

    后文也把FILE* stream叫做文件流.

    FILE结构体中也有缓冲区相关操作的成员变量, 这里没有关注.

    3.1.1 缓存区的作用

    以下内容摘自: https://blog.csdn.net/qq_35116371/article/details/71426827

    (1)非缓冲的文件操作访问方式: 每次对文件进行一次读写操作时,都需要使用读写系统调用来处理此操作,即需要执行一次系统调用,执行一次系统调用将涉及到CPU状态的切换,即从用户空间切换到内核空间,实现进程上下文的切换,这将损耗一定的CPU时间,频繁的磁盘访问对程序的执行效率造成很大的影响。
    (2)缓冲的文件操作访问方式: ANSI标准C库函数是建立在底层的系统调用之上,即C函数库文件访问函数的实现中使用了低级文件I/O系统调用,ANSI标准C库中的文件处理函数为了减少使用系统调用的次数,提高效率,采用缓冲机制,这样,可以在磁盘文件进行操作时,可以一次从文件中读出大量的数据到缓冲区中,以后对这部分的访问就不需要再使用系统调用了,即需要少量的CPU状态切换,提高了效率。
    ————————————————
    版权声明:本文为CSDN博主「rushingw」的原创文章,遵循 CC 4.0 BY-SA
    版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_35116371/article/details/71426827

    3.1.2 缓存区分类

    以下简要摘自: https://blog.csdn.net/qq_35116371/article/details/71426827

    1. 全缓冲区: 只有缓冲区满, 才执行系统调用处理
      • 使用: 对于磁盘文件的操作通常使用全缓冲的方式访问
    2. 行缓存区: 当在输入和输出中遇到换行符时,执行系统调用处理
      • 使用: 当所操作的流涉及一个终端时(例如标准输入stdin和标准输出stdout),使用行缓冲方式
    3. 无缓冲区: 如名, 略

    3.1.3 缓冲区的优缺点

    • 优点: 减少CPU切换消耗, 提高效率
    • 缺点: 缓冲区都会面临同步问题, 或者说实时性问题, 例如全缓冲模式, 必须缓冲区满才进行操作

    下一节介绍利用fflush函数强制进行操作解决缺点.

    3.2 fflush函数

    正是因为缓冲区带来的实时性问题, 才会有fflush这类函数.

    1. fflush(stdin): 清空stdin的缓冲区
    2. fflush(stdout): 把stdout缓冲区中的内容全部输出(操作完成后缓冲区也就空了)
    • stdin缓冲区: 当我们输入时, 实际是先把数据写入到了stdin的缓冲区, 只有满足条件时才会写入到变量中, 例如行缓存区模式下, 遇到换行符才会执行系统调用(见缓冲区分类)
    • stdout缓冲区: 当我们使用printf类似的函数希望将字符输出到终端上时, 是先把数据写入到stdout的缓冲区, 只有满足条件时才会输出, fflush相当于强制输出了

    3.3 使用场景示例

    为防止之前输入可能还有残留(比如没有全部写入到变量中), 可以执行fflush清空.

    //为防止之前输入可能还有残留(比如没有全部写入到变量中), 可以执行fflush
    fflush(stdin);
    fgets(buf, 10, stdin);
    

    为希望立即将某些字符串立即打印到终端上, 可以执行fflush.

    //为希望立即将某些字符串立即打印到终端上, 可以执行fflush.
    printf("hello world");
    fflush(stdout);
    

    1. 参考网址

    1. 大神FILE结构体的介绍: https://blog.csdn.net/qq_35116371/article/details/71426827
    2. _fileno函数: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fileno?view=vs-2019
    3. _fileno函数: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxbd00/rtfil.htm
  • 相关阅读:
    二叉排序树(B-Tree)-c实现
    队列(Queue)-c实现
    栈(stack)--c实现(使用双链表)
    链表(list)--c实现
    c 和 指针读书笔记(1)
    c traps and pitfalls reading notes(2)
    js 控制
    正则表达式
    Android 笔记
    Android 布局方式学习
  • 原文地址:https://www.cnblogs.com/whuwzp/p/stdin_stdout_fflush_file.html
Copyright © 2011-2022 走看看