刨根问底系列(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\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);
write
:- 系统调用, 通过unistd.h头也可以知道, 因为unistd.h 中所定义的接口通常都是大量针对类Unix系统调用的封装
- 参数是文件描述符
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
- 全缓冲区: 只有缓冲区满, 才执行系统调用处理
- 使用: 对于磁盘文件的操作通常使用全缓冲的方式访问
- 行缓存区: 当在输入和输出中遇到换行符时,执行系统调用处理
- 使用: 当所操作的流涉及一个终端时(例如标准输入stdin和标准输出stdout),使用行缓冲方式
- 无缓冲区: 如名, 略
3.1.3 缓冲区的优缺点
- 优点: 减少CPU切换消耗, 提高效率
- 缺点: 缓冲区都会面临同步问题, 或者说实时性问题, 例如全缓冲模式, 必须缓冲区满才进行操作
下一节介绍利用fflush函数强制进行操作解决缺点.
3.2 fflush函数
正是因为缓冲区带来的实时性问题, 才会有fflush这类函数.
fflush(stdin)
: 清空stdin的缓冲区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. 参考网址
- 大神FILE结构体的介绍: https://blog.csdn.net/qq_35116371/article/details/71426827
_fileno
函数: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fileno?view=vs-2019_fileno
函数: https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.bpxbd00/rtfil.htm
刨根问底系列(2)——stdin、stdout、FILE结构体、缓冲区和fflush的理解的更多相关文章
- C语言文件操作 FILE结构体
内存中的数据都是暂时的,当程序结束时,它们都将丢失.为了永久性的保存大量的数据,C语言提供了对文件的操作. 1.文件和流 C将每个文件简单地作为顺序字节流(如下图).每个文件用文件结束符结束,或者在特 ...
- Pwn with File结构体(四)
前言 前面几篇文章说道,glibc 2.24 对 vtable 做了检测,导致我们不能通过伪造 vtable 来执行代码.今天逛 twitter 时看到了一篇通过绕过 对vtable 的检测 来执行代 ...
- Linux_Struct file()结构体
struct file结构体定义在/linux/include/linux/fs.h(Linux 2.6.11内核)中,其原型是:struct file { /* * f ...
- Linux--struct file结构体
struct file(file结构体): struct file结构体定义在include/linux/fs.h中定义.文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 ...
- 2018.5.2 file结构体
f_flags,File Status Flag f_pos,表示当前读写位置 f_count,表示引用计数(Reference Count): dup.fork等系统调用会导致多个文件描述符指向同一 ...
- Pwn with File结构体(一)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 利用 FILE 结构体进行攻击,在现在的 ctf 比赛中也经常出现 ...
- Pwn with File结构体(三)
前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 前面介绍了几种 File 结构体的攻击方式,其中包括修改 vtab ...
- fd与FILE结构体
文件描述符 fd 概念:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件 ...
- file结构体中private_data指针的疑惑
转:http://www.360doc.com/content/12/0506/19/1299815_209093142.shtml hi all and barry, 最近在学习字符设备驱动,不太明 ...
随机推荐
- sql 语句系列(字符串的遍历嵌入删除与统计)[八百章之第十一章]
遍历字符串 我觉得首先要提出一个疑问: 一个数据库本身就是用于存储的,遍历字符串究竟有何意义? 先看如何实现的,毕竟sql service 是没有for循环,也没有loop和while的. selec ...
- 洛谷 P2568 GCD 题解
原题链接 庆祝一下:数论紫题达成成就! 第一道数论紫题.写个题解庆祝一下吧. 简要题意:求 \[\sum_{i=1}^n \sum_{j=1}^n [gcd(i,j)==p] \] 其中 \(p\) ...
- 进制-Adding Two Negabinary Numbers
2020-02-20 14:52:41 问题描述: 问题求解: 最开始的想法是将两个数字先转化成自然数在求和,最后转化回去,但是实际上这种方案是不可取的,主要的问题就是会爆掉. 那么就得按位进行运算了 ...
- Trie树的简单实现
import java.util.ArrayList; import java.util.TreeMap; import util.FileOperation; public class Trie { ...
- tpyboard v202 测试tcp通讯,i2c的oled程序,呼吸灯源码,希望对大家有所帮助
1.下载到板子里的main.py代码如果需要驱动oled的,可以参考我上面那篇文章import time, mathimport machineimport network# from ssd1306 ...
- JavaScript模块化-CommonJS、AMD、CMD、UMD、ES6
前言:模块化开发需求 在JS早期,使用script标签引入JS,会造成以下问题: 加载的时候阻塞网页渲染,引入JS越多,阻塞时间越长. 容易污染全局变量. js文件存在依赖关系,加载必须有顺序.项目较 ...
- 10行Python代码计算汽车数量
当你还是个孩子坐车旅行的时候,你玩过数经过的汽车的数目的游戏吗? 在这篇文章中,我将教你如何使用10行Python代码构建自己的汽车计数程序. 以下是环境及相应的版本库: Python版本 3.6.9 ...
- 多伦多大学&NVIDIA最新成果:图像标注速度提升10倍!
图像标注速度提升10倍! 这是多伦多大学与英伟达联合公布的一项最新研究:Curve-GCN的应用结果. Curve-GCN是一种高效交互式图像标注方法,其性能优于Polygon-RNN++.在自动模式 ...
- 模块 heapq_堆排序
_heapq_堆排序 该模块提供了堆排序算法的实现.堆是二叉树,最大堆中父节点大于或等于两个子节点,最小堆父节点小于或等于两个子节点. 创建堆 heapq有两种方式创建堆, 一种是使用一个空列表,然后 ...
- javascript创建函数的方法
函数对任何语言来说都是一个核心的概念.函数,是一种封装(将一些语句,封装到函数里面). 通过函数可以封装任意多条语句,而且可以在任何地方.任何时候调用执行. ECMAScript中的函数使用funct ...