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\n", stdin->_fileno);
printf("stdout->fileno = %d\n", stdout->_fileno);
printf("stderr->fileno = %d\n", 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

刨根问底系列(2)——stdin、stdout、FILE结构体、缓冲区和fflush的理解的更多相关文章

  1. C语言文件操作 FILE结构体

    内存中的数据都是暂时的,当程序结束时,它们都将丢失.为了永久性的保存大量的数据,C语言提供了对文件的操作. 1.文件和流 C将每个文件简单地作为顺序字节流(如下图).每个文件用文件结束符结束,或者在特 ...

  2. Pwn with File结构体(四)

    前言 前面几篇文章说道,glibc 2.24 对 vtable 做了检测,导致我们不能通过伪造 vtable 来执行代码.今天逛 twitter 时看到了一篇通过绕过 对vtable 的检测 来执行代 ...

  3. Linux_Struct file()结构体

    struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file {        /*         * f ...

  4. Linux--struct file结构体

    struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的  ...

  5. 2018.5.2 file结构体

    f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...

  6. Pwn with File结构体(一)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 利用 FILE 结构体进行攻击,在现在的 ctf 比赛中也经常出现 ...

  7. Pwn with File结构体(三)

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 前面介绍了几种 File 结构体的攻击方式,其中包括修改 vtab ...

  8. fd与FILE结构体

    文件描述符 fd 概念:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件 ...

  9. file结构体中private_data指针的疑惑

    转:http://www.360doc.com/content/12/0506/19/1299815_209093142.shtml hi all and barry, 最近在学习字符设备驱动,不太明 ...

随机推荐

  1. Spring Cloud 系列之 Netflix Hystrix 服务容错

    什么是 Hystrix Hystrix 源自 Netflix 团队于 2011 年开始研发.2012年 Hystrix 不断发展和成熟,Netflix 内部的许多团队都采用了它.如今,每天在 Netf ...

  2. CentOS7安装和配置ftp服务

    目录 一.ftp简介 二.安装ftp软件包 1.安装ftp服务器 2.安装ftp客户端 三.配置ftp服务器 1.关闭SELINUX 2.配置ftp数据端口参数 3.开通防火墙 4.启动vsftpd服 ...

  3. 前端html学习笔记

    一 HTML介绍 1. Web服务的本质 import socket sk = socket.socket() sk.bind(("127.0.0.1", 8080)) sk.li ...

  4. centos 安装activeMq

    Apache ActiveMQ是一个免费的开源消息代理和集成模式服务器.它支持来自JAVA.c++.C.Python.Perl.PHP等多种语言的客户端和协议.它提供了许多功能,如消息组.虚拟目的地. ...

  5. [单调栈] 2018-2019 ACM-ICPC, China Multi-Provincial Collegiate Programming Contest-Maximum Element In A Stack

    题目:https://codeforces.com/gym/102222/problem/A Maximum Element In A Stack time limit per test 10.0 s ...

  6. [概率] HDU 2019 Multi-University Training Contest 10 - Valentine's Day

    Valentine's Day Time Limit: 2000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others ...

  7. [状态压缩,折半搜索] 2019牛客暑期多校训练营(第九场)Knapsack Cryptosystem

    链接:https://ac.nowcoder.com/acm/contest/889/D来源:牛客网 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 262144K,其他语言52428 ...

  8. vscode快速生成html模板(vscode快捷键"!"生成html模板)

    问题: 在vscode中新建test.html, 内容是空白的,输入"!",然后按tap键 ,没有生成常见的html模板,也就是如下: 输入! html html:5 DOCTYP ...

  9. Building Applications with Force.com and VisualForce (DEV401) (四):Building Your user Interface

    Dev 401-004:Application essential:Building Your user Interface: Module Agenda1.Custom Applications2. ...

  10. Building Applications with Force.com and VisualForce (DEV401) (二二):Visualforce Componets (Tags) Library Part II

    Dev401-023:Visualforce Pages: Visualforce Componets (Tags) Library Part II   Apex:pageBlockTable1.A ...