当前位置: 首页 > news >正文

Linux-应用编程学习笔记(二、文件I/O、标准I/O)

一、文件I/O基础

文件 I/O 指的是对文件的输入/输出操作,就是对文件的读写操作。Linux 下一切皆文件。

1.1 文件描述符

在 open函数执行成功的情况下, 会返回一个非负整数, 该返回值就是一个文件描述符(file descriptor) , 这说明文件描述符是一个非负整数;

对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。

在 Linux 系统中,一个进程可以打开的文件数是有限制的。默认1024个(0~1023)系统占用:系统标准输入(0)、 标准输出(1)以及标准错误(2)。

通过 ulimit 命令来查看进程可打开的最大文件数:

ulimit -n

Tips:每一个硬件设备都会对应于linux系统下的某一个文件(设备文件),应用程序对设备文件进行读写操作来使用或控制硬件设备。 

1.1.1 复制文件描述符

复制得到的文件描述符和旧的文件描述符拥有相同的权限,但是号是不一样的。可以多次复制。

“复制”的含义实则是复制文件表。

  • dup 函数用于复制文件描述符,可以实现两个写入的接续进行。
#include <unistd.h>
int dup(int oldfd);函数参数和返回值含义如下:
oldfd: 需要被复制的文件描述符。
返回值: 成功时将返回一个新的文件描述符,由操作系统分配,分配置原则遵循文件描述符分配原则;
如果复制失败将返回-1,并且会设置 errno 值。
  • dup2也可以复制文件描述符,并且可以手动指定文件描述符。
#include <unistd.h>
int dup2(int oldfd, int newfd);函数参数和返回值含义如下:
oldfd: 需要被复制的文件描述符。
newfd: 指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)。
返回值: 成功时将返回一个新的文件描述符,也就是手动指定的文件描述符 newfd;如果复制失败将返
回-1,并且会设置 errno 值。

1.2 open打开文件(文件权限)

可以通过man命令查看系统调用的信息。

man 2 open/*
man 命令后面跟着两个参数:
数字 2 表示系统调用, Linux 命令(对应数字 1)以及标准 C 库函数(对应数字 3)所对应的帮助信息。
最后一个参数 open 表示需要查看的系统调用函数名。
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);int creat(const char *pathname, mode_t mode);int openat(int dirfd, const char *pathname, int flags);
int openat(int dirfd, const char *pathname, int flags, mode_t mode);

函数参数和返回值含义如下:

pathname: 
字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径) 信息,譬如:"./src_file"(当前目录下的 src_file 文件)、 "/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。

flags: 
调用 open 函数时需要提供的标志, 包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述,都是常量, open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一个标志,也可以通过位或运算(|) 将多个标志进行组合。 这些标志介绍如下:

  • O_RDONLY:只读。    
  • O_WRONLY:只写。    
  • O_RDWR:可读可写。    
  • O_CREAT:不存在则创建。    
  • O_DIRECTORY:pathname指向的不是目录,调用open失败。    
  • O_EXCL:与O_CREAT一起使用,判断是否已经存在。    
  • O_NOFOLLOW:pathname是一个符号链接,将不对其进行解引用,直接返回错误。
  • O_TRUNC :将文件原本的内容全部丢弃,文件大小变为 0。
  • O_APPEND :调用 open 函数打开文件,当每次使用 write()函数对文件进行写操作时,都会自动把文件当前位置偏移量移动到文件末尾, 从文件末尾开始写入数据,也就是意味着每次写入数据都是从文件末尾开始。使用了 O_APPEND 标志,即使是通过 lseek 函数也是无法修改写文件时对应的位置偏移量。
  • O_DSYNC:每个write()之后,自动调用fdatasync()进行数据同步
  • O_SYNC:每个write()之后,自动调用fsync()进行数据同步

mode: 
此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志
时才有效(O_TMPFILE 标志用于创建一个临时文件)。 权限对于文件来说是一个很重要的属性,那么在 Linux系统中,我们可以通过 touch 命令新建一个文件,此时文件会有一个默认的权限,如果需要修改文件权限,可通过 chmod 命令对文件权限进行修改,譬如在 Linux 系统下我们可以使用"ls -l"命令来查看到文件所对应的权限。mode 参数的类型是 mode_t(u32)

S---这 3 个 bit 位用于表示文件的特殊权限,文件特殊权限一般用的比较少;

U---这 3 个 bit 位用于表示文件所属用户的权限,即文件或目录的所属者;

G---这 3 个 bit 位用于表示同组用户(group)的权限,即与文件所有者有相同组 ID 的所有用户;

O---这 3 个 bit 位用于表示其他用户的权限;

3 个 bit 位中,按照 rwx 顺序来分配权限位。例如:7(八进制),可读可写可执行,5(八进制),可读可执行。

open 函数文件权限宏:(通过位或可以组合)

宏定义说明
S_IRUSR允许文件所属者读文件
S_IWUSR允许文件所属者写文件
S_IXUSR允许文件所属者执行文件
S_IRWXU允许文件所属者读、写、执行文件
S_IRGRP允许同组用户读文件
S_IWGRP允许同组用户写文件
S_IXGRP允许同组用户执行文件
S_IRWXG允许同组用户读、写、执行文件
S_IROTH允许其他用户读文件
S_IWOTH允许其他用户写文件
S_IXOTH允许其他用户执行文件
S_IRWXO允许其他用户读、写、执行文件
S_ISUID
S_ISGID
S_ISVTX
set-user-ID(特殊权限)
set-group-ID(特殊权限)
sticky(特殊权限)

返回值

成功将返回文件描述符,文件描述符是一个非负整数;失败将返回-1。

1.3 write写文件

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

函数参数和返回值含义如下:
fd: open之后返回的文件描述符。 关于文件描述符,需要将进行写操作的文件所对应的文件描述符传递给 write 函数。
buf: 指定写入数据对应的缓冲区。
count: 指定写入的字节数
返回值: 如果成功将返回写入的字节数(0 表示未写入任何字节),如果此数字小于 count 参数,这不是错误,譬如磁盘空间已满,可能会发生这种情况;如果写入出错,则返回-1。

一般是默认写文件写到文件起始位置。如果当前位置偏移量为 1000 个字节处,调用 write()写入或 read()读取 500 个字节之后,当前位置偏移量将会移动到 1500 个字节处。

1.4 read读文件

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

函数参数和返回值含义如下:
fd: 文件描述符。与 write 函数的 fd 参数意义相同。

buf: 指定用于存储读取数据的缓冲区。

count: 指定需要读取的字节数。

返回值: 如果读取成功将返回读取到的字节数,实际读取到的字节数(没那么多可以读的字节)可能会小于 count 参数指定的字节数,也有可能会为 0。

1.5 close关闭文件

#include <unistd.h>
int close(int fd);

fd: 文件描述符,需要关闭的文件所对应的文件描述符。
返回值: 如果成功返回 0,如果失败则返回-1。

进程终止的时候,内核会自动关闭进程打开的所有文件。显式关闭不再需要的文件描述符是良好的编程习惯,文件描述符是有限资源。

1.6 lseek

系统会自动记录读写位置偏移量。文件第一个字节数据的位置偏移量为 0。
当打开文件时,会将读写偏移量设置为指向文件开始位置处,以后每次调用 read()、 write()将自动对其进行调整,以指向已读或已写数据后的下一字节,因此,连续的调用 read()和 write()函数将使得读写按顺序递增,对文件进行操作。

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);

fd: 文件描述符。
offset: 偏移量,以字节为单位。
whence: 用于定义参数 offset 偏移量对应的参考值, 该参数为下列其中一种(宏定义) :

  • SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算) ;
  • SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处, offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
  • SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。

返回值: 成功将返回从文件头部开始算起的位置偏移量(字节为单位), 也就是当前的读写位置; 发生错误将返回-1。

二、文件I/O深入

2.1 Linux系统如何管理文件

2.1.1 静态文件和inode

静态文件:文件存放在磁盘文件系统中,并且以一种固定的形式进行存放。

文件储存在硬盘上, 硬盘的最小存储单位叫做“扇区” (Sector), 每个扇区储存 512 字节(相当于 0.5KB),操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个“” (block)。这种由多个扇区组成的“块” ,是文件存取的最小单位。 “块” 的大小,最常见的是 4KB,即连续八个 sector 组成一个 block。

磁盘两个区域:数据区(用于存储文件中的数据),inode区(用于存放 inode table(inode 表))。

通过"ls -i"命令或stat 命令查看文件的 inode 编号。

打开一个文件,系统内部会将这个过程分为三步:
1) 系统找到这个文件名所对应的 inode 编号;
2) 通过 inode 编号从 inode table 中找到对应的 inode 结构体;
3) 根据 inode 结构体中记录的信息,确定文件数据所在的 block,并读出数据。


2.1.2 文件打开时的状态

当我们调用 open 函数去打开文件的时候,内核会申请一段内存(一段缓冲区) ,并且将静态文件的数据内容从磁盘这些存储设备中读取到内存中进行管理、 缓存(也把内存中的这份文件数据叫做动态文件、内核缓冲区)。

读写动态文件后,动态文件与静态文件不同步,内核会将动态文件更新至磁盘设备中。

在 Linux 系统中, 内核会为每个进程设置一个专门的数据结构用于管理该进程,譬如用于记录进程的状态信息、运行特征等,我们把这个称为进程控制块(Process control block,缩写PCB) 。

PCB 数据结构体中有一个指针指向了文件描述符表(File descriptors), 文件描述符表中的每一个元素索引到对应的文件表(File table),文件表也是一个数据结构体,其中记录了很多文件相关的信息,譬如文件状态标志、 引用计数、 当前文件的读写偏移量以及 i-node 指针(指向该文件对应的 inode)等, 进程打开的所有文件对应的文件描述符都记录在文件描述符表中,每一个文件描述符都会指向一个对应的文件表。

2.2 返回错误处理与errno

errno 本质上是一个 int 类型的变量,用于存储错误编号, 但是需要注意的是,并不是执行所有的系统调用或 C 库函数出错时,操作系统都会设置 errno。

通过man命令可以查询返回值。

#include <errno.h>

2.2.1 strerror函数(C库函数)

将对应的 errno 转换成适合我们查看的字符串信息

#include <string.h>
char *strerror(int errnum);函数参数和返回值如下:
errnum: 错误编号 errno。
返回值: 对应错误编号的字符串描述信息。

2.2.2 perror函数(C库函数)

调用此函数不需要传入 errno,函数内部会自己去获取 errno 变量的值, 调用此函数会直接将错误提示字符串打印出来,而不是返回字符串,除此之外还可以在输出的错误提示字符串之前加入自己的打印信息。perror 函数会在附加信息后面自动加入冒号和空格以区分

#include <stdio.h>
void perror(const char *s);函数参数和返回值含义如下:
s: 在错误提示字符串信息之前,可加入自己的打印信息,也可不加,不加则传入空字符串即可。
返回值: void 无返回值。

2.3终止进程

终止进程的方法:

  • main 函数中运行 return;
  • 调用 Linux 系统调用_exit()或_Exit();
  • 调用 C 标准库函数 exit()。

2.3.1 _exit、_Exit

调用_exit()函数会清除其使用的内存空间,并销毁其在内核中的各种数据结构, 关闭进程的所有文件描述符, 并结束进程、将控制权交给操作系统。_Exit与_exit一样。

_exit()和_Exit()是系统调用

#include <unistd.h>
void _exit(int status);status:0 表示正常结束、若为其它值则表示程序执行过程中检测到有错误发生。

2.3.2 exit

exit()是一个标准 C 库函数

#include <stdlib.h>
void exit(int status);

2.4 空洞文件

使用lseek函数将文件偏移量(6000字节)超过文件长度(4096字节),write开始在6000写,在4096~6000之间没有写入任何数据,就形成了文件空洞,那么该文件就是空洞文件。文件空洞不占用物理空间。直到在某个时刻对空洞部分进行写入数据时才会为它分配对应的空间,但是空洞文件形成时,逻辑上该文件的大小是包含了空洞部分的大小的,这点需要注意。


空洞文件的用处:对于很大的文件,可以使用多线程进行分段读写。

使用 ls 命令查看到的大小是文件的逻辑大小,包括了空洞部分大小和真实数据部分大小; du 命令查看到的大小是文件实际占用存储块的大小


2.5 多次打开同一文件

  • 一个进程内多次 open 打开同一个文件,那么会得到多个不同的文件描述符 fd,同理在关闭文件的时候也需要调用 close 依次关闭各个文件描述符。
  • 一个进程内多次 open 打开同一个文件,在内存中并不会存在多份动态文件。
  • 一个进程内多次 open 打开同一个文件,在不同文件描述符所对应的读写位置偏移量是相互独立的。
  • 分别写入同一文件,现象是:第二次写入的覆盖第一次写入的。想要接着写就用O_APPEND。

2.6 文件共享

同一个文件(譬如磁盘上的同一个文件,对应同一个 inode) 被多个独立的读写体同时进行 IO 操作(一个读写体操作文件尚未调用 close 关闭的情况下,另一个读写体去操作文件)。

常见的三种文件共享的实现方式
(1)同一个进程中多次调用 open 函数打开同一个文件。

多次调用open,得到不同的文件描述符,对应多个不同地文件表,文件表索引到同一inode。

(2)不同进程中分别使用 open 函数打开同一个文件。

(3)同一个进程中通过 dup(dup2)函数对文件描述符进行复制。

2.7 竞争冒险

操作共享资源的两个进程(或线程),其操作之后的所得到的结果往往是不可预期的, 因为每个进程(或线程)去操作文件的顺序是不可预期的,即这些进程获得 CPU 使用权的先后顺序是不可预期的,完全由操作系统调配, 这就是所谓的竞争状态。

2.8 原子操作

所谓原子操作, 是由多步操作组成的一个操作,原子操作要么一步也不执行,一旦执行, 必须要执行完所有步骤,不可能只执行所有步骤中的一个子集。

  • O_APPEND实现原子操作

移动当前写位置偏移量到文件末尾、写入数据”这两个操作步骤就组成了一个原子操作。

  • pread()和pwrite()
#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t count, off_t offset);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);函数参数和返回值含义如下:
fd、 buf、 count 参数与 read 或 write 函数意义相同。
offset: 表示当前需要进行读或写的位置偏移量。
返回值: 返回值与 read、 write 函数返回值意义一样。

调用 pread 函数时,无法中断其定位和读操作(也就是原子操作);不更新文件表中的当前位置偏移量

  • 创建一个文件(O_EXCL 标志)

文件创建的竞争状态:

进程 A 和进程 B 都会创建出同一个文件,同一个文件被创建两次这是不允许的。

将“判断文件是否存在、创建文件”这两个步骤合成为一个原子操作

2.9 fcntl和ioctl

#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ )函数参数和返回值含义如下:
fd: 文件描述符。
cmd: 对fd进行的操作命令。cmd 操作命令大致可以分为以下 5 种功能:
1.复制文件描述符(cmd=F_DUPFD 或 cmd=F_DUPFD_CLOEXEC);
2.获取/设置文件描述符标志(cmd=F_GETFD 或 cmd=F_SETFD);
3.获取/设置文件状态标志(cmd=F_GETFL 或 cmd=F_SETFL);
4.获取/设置异步 IO 所有权(cmd=F_GETOWN 或 cmd=F_SETOWN);
5.获取/设置记录锁(cmd=F_GETLK 或 cmd=F_SETLK);
fcntl 函数是一个可变参函数,第三个参数需要根据不同的 cmd 来传入对应的实参,配合 cmd 来使
用。
返回值: 执行失败情况下,返回-1,并且会设置 errno;执行成功的情况下,其返回值与 cmd(操作命
令)有关,譬如 cmd=F_DUPFD(复制文件描述符)将返回一个新的文件描述符、 cmd=F_GETFD(获取文
件描述符标志)将返回文件描述符标志、 cmd=F_GETFL(获取文件状态标志)将返回文件状态标志等。

一般用于操作特殊文件或硬件外设。#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);函数参数和返回值含义如下:
fd: 文件描述符。
request: 此参数与具体要操作的对象有关,没有统一值, 表示向文件描述符请求相应的操作;
...: 此函数是一个可变参函数, 第三个参数需要根据 request 参数来决定,配合 request 来使用。
返回值: 成功返回 0,失败返回-1。

2.10 截断文件

截断:如果文件目前的大小大于参数 length 所指定的大小,则多余的数据将被丢失,类似于多余的部分被“砍”掉了;如果文件目前的大小小于参数 length 所指定的大小,则将其进行扩展, 对扩展部分进行读取将得到空字节"\0"。

#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);ftruncate()使用文件描述符 fd 来指定目标文件。
truncate()则直接使用文件路径 path 来指定目标文件。

使用 ftruncate()函数进行文件截断操作之前,必须调用 open()函数打开该文件得到文件描述符,并且必须要具有可写权限,也就是调用 open()打开文件时需要指定 O_WRONLY 或 O_RDWR。
调用这两个函数并不会导致文件读写位置偏移量发生改变,所以截断之后一般需要重新设置文件当前的读写位置偏移量,以免由于之前所指向的位置已经不存在而发生错误(譬如文件长度变短了,文件当前所指向的读写位置已不存在)。
 

三、标准I/O库

3.1 标准I/O库简介

<stdio.h>

标准 I/O 和文件 I/O 的区别如下:

  • 虽然标准 I/O 和文件 I/O 都是 C 语言函数,但是标准 I/O 是标准 C 库函数,而文件 I/O 则是 Linux系统调用
  • 标准 I/O 是由文件 I/O 封装而来,标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的;
  • 可移植性:标准 I/O 相比于文件 I/O 具有更好的可移植性,通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的;而对于标准 I/O 来说,由于很多操作系统都实现了标准 I/O 库,标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的,所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性。
  • 性能、效率: 标准 I/O 库在用户空间维护了自己的 stdio 缓冲区, 所以标准 I/O 是带有缓存的,而文件 I/O 在用户空间是不带有缓存的,所以在性能、效率上,标准 I/O 要优于文件 I/O。

3.2 FILE指针

对于标准 I/O 库函数来说,它们的操作是围绕 FILE 指针进行的,当使用标准 I/O 库函数打开或创建一个文件时,会返回一个指向 FILE 类型对象的指针(FILE *) ,使用该 FILE 指针与被打开或创建的文件相关联,然后该 FILE 指针就用于后续的标准 I/O 操作(使用标准 I/O 库函数进行 I/O 操作),所以由此可知,FILE 指针的作用相当于文件描述符。

FILE 是一个结构体数据类型,它包含了标准 I/O 库函数为管理文件所需要的所有信息,包括用于实际I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等。


3.3 标准输入、标准输出和标准错误

#include <unistd.h>/* Standard file descriptors. */
#define STDIN_FILENO 0 /* Standard input. */
#define STDOUT_FILENO1 /* Standard output. */
#define STDERR_FILENO2 /* Standard error output. */
#include <stdio.h>/* Standard streams. */
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. */
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr

3.4 fopen、fclose

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);函数参数和返回值含义如下:
path: 参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
mode: 参数 mode 指定了对该文件的读写权限,是一个字符串,稍后介绍。
返回值: 调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,
后续的标准 I/O 操作将围绕 FILE 指针进行。 如果失败则返回 NULL,并设置 errno 以指示错误原因。
  • r = O_RDONLY
  • r+ = O_RDWR
  • w = O_WRONLY| O_CREAT|O_TRUNC
  • w+ =  O_RDWR| O_CREAT|O_TRUNC
  • a = O_WRONLY| O_CREAT|O_APPEND
  • a+ = O_RDWR| O_CREAT|O_APEND

新建文件的权限:默认值S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)

#include <stdio.h>
int fclose(FILE *stream);

3.5 fread、fwrite

#include <stdio.h>size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);ptr: fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
size: fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大
小为 nmemb * size 个字节。
nmemb: 参数 nmemb 指定了读取数据项的个数。
stream: FILE 指针。
返回值: 调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数
size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb, fread()不能区分文件结尾和错误。size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);ptr: 将参数 ptr 指向的缓冲区中的数据写入到文件中。
size: 参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同。
nmemb: 参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同。
stream: FILE 指针。
返回值: 调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 size
等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)。

3.6 fseek定位

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
//用于设置文件读写位置偏移量。函数参数和返回值含义如下:
stream: FILE 指针。
offset: 与 lseek()函数的 offset 参数意义相同。
whence: 与 lseek()函数的 whence 参数意义相同。
返回值: 成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因; 与 lseek()函数的返回值
意义不同,这里要注意!#include <stdio.h>
long ftell(FILE *stream);
//用于获取文件当前的读写位置偏移量。

3.7 检测或复位状态

1.feof函数

用于测试参数 stream 所指文件的 end-of-file 标志。

#include <stdio.h>
int feof(FILE *stream);

2.ferror函数

用于测试参数 stream 所指文件的错误标志。

#include <stdio.h>
int ferror(FILE *stream);

3.clearerr函数

用于清除 end-of-file 标志和错误标志。

#include <stdio.h>
void clearerr(FILE *stream);

3.8 格式化I/O

1.格式化输出

#include <stdio.h>
int printf(const char *format, ...);
//将程序中的字符串信息输出显示到终端int fprintf(FILE *stream, const char *format, ...);
//将格式化数据写入到由 FILE 指针指定的文件中int dprintf(int fd, const char *format, ...);
//将格式化数据写入到由文件描述符 fd 指定的文件中int sprintf(char *buf, const char *format, ...);
//将格式化数据存储在由参数 buf 所指定的缓冲区中int snprintf(char *buf, size_t size, const char *format, ...);
//与sprintf一样,为了防止缓冲区溢出,加入了参数size,多出的将丢弃。

printf举例:%[flags][width][.precision][length]typeflags: 标志,可包含 0 个或多个标志;
width: 输出最小宽度,表示转换后输出字符串的最小宽度;
precision: 精度,前面有一个点号" . ";
length: 长度修饰符;
type: 转换类型,指定待转换数据的类型。

type:

  • d/i:  int
  • o:    unsigned int,无符号八进制
  • u:    unsigned int,无符号十进制
  • x/X: unsigned int,无符号十六进制,大小写
  • f/F: double,浮点数,默认保留小数后六位
  • e/E: double,科学计数法表示浮点数
  • g/G: double,根据数值的长度,选择以最短的方式输出
  • s:   char *,字符串
  • p:   void *,十六进制表示的指针
  • c:   char,字符型,可以把输入的数字转换到 ASCII 码字符输出

flags:

  • #:带前缀
  • 0:在输出的数字前补0
  • -:左对齐
  • ‘ ’(空格):正数前面加空格,负数前面加负号
  • +:输出正数的正号

width:

最小的输出宽度,用十进制数来表示输出的最小位数,若实际的输出位数大于指定的输出的最小位数,则以实际的位数进行输出,若实际的位数小于指定输出的最小位数,则可按照指定的 flags 标志补 0 或补空格。

precision精度:显示小数点后的位数。

length:指明待转换数据的长度。

2.格式化输入

#include <stdio.h>
int scanf(const char *format, ...);
//将用户输入(标准输入)的数据进行格式化转换并进行存储int fscanf(FILE *stream, const char *format, ...);
//从指定文件中读取数据,作为格式转换的输入数据int sscanf(const char *str, const char *format, ...);
//从参数 str 所指向的字符串缓冲区中读取数据,作为格式转换的输入数据

format:

%[*][width][length]type
%[m][width][length]typewidth: 最大字符宽度;
length: 长度修饰符,与格式化输出函数的 format 参数中的 length 字段意义相同。
type: 指定输入数据的类型。与printf一致。

3.9 I/O缓冲

1.文件I/O的内核缓冲

read()和 write()系统调用在进行文件读写操作的时候并不会直接访问磁盘设备,而是仅仅在用户空间缓冲区和内核缓冲区(kernel buffer cache)之间复制数据。

2.刷新文件I/O的内核缓冲区

控制文件I/O内核缓冲的系统调用

#include <unistd.h>int fsync(int fd);
//将参数 fd 所指文件的内容数据和元数据写入磁盘,写完之后返回int fdatasync(int fd);
//与fsync类似,只写入内容数据,不写元数据(记录文件属性相关的数据信息,比如文件大小、时间戳、权限等)void sync(void);
//刷新所有文件 I/O 内核缓冲区

控制文件I/O内核缓冲的标志

fd = open(filepath, O_WRONLY | O_DSYNC);
//在每个 write()调用之后调用 fdatasync()函数进行数据同步fd = open(filepath, O_WRONLY | O_SYNC);
//在每个 write()调用之后调用 fsync()函数进行数据同步

对性能的影响
在程序中频繁调用 fsync()、 fdatasync()、 sync()(或者调用 open 时指定 O_DSYNC 或 O_SYNC 标志)对性能的影响极大,大部分不会使用。
 

3.直接I/O:绕过内核缓冲

Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区,从用户空间直接将数据传递到文件或磁盘设备。

fd = open(filepath, O_WRONLY | O_DIRECT);

直接 I/O 的对齐限制

⚫ 应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐;
⚫ 写文件时,文件的位置偏移量必须是块大小的整数倍;
⚫ 写入到文件的数据大小必须是块大小的整数倍。

如何确定磁盘分区的块大小呢?可以使用 tune2fs 命令进行查看
tune2fs -l /dev/sda1 | grep "Block size"

4.stdio缓冲

对 stdio 缓冲进行设置

#include <stdio.h>int setvbuf(FILE *stream, char *buf, int mode, size_t size);
stream: FILE 指针,用于指定对应的文件。
buf:指向 size 大小的内存区域将作为该文件的 stdio 缓冲区
mode: 参数 mode 用于指定缓冲区的缓冲类型:_IONBF(不缓冲),_IOLBF(行缓冲),_IOFBF(全缓冲)。
size: 指定缓冲区的大小。
返回值: 成功返回 0,失败将返回一个非 0 值,并且会设置 errno 来指示错误原因。void setbuf(FILE *stream, char *buf);
buf:NULL无缓冲,或者BUFSIZ字节的缓冲区void setbuffer(FILE *stream, char *buf, size_t size);
//允许调用者指定 buf 缓冲区的大小

刷新stdio缓冲区

#include <stdio.h>
int fflush(FILE *stream);
//stream 指定需要进行强制刷新的文件,如果该参数设置为 NULL,则表示刷新所有的 stdio 缓冲区调用 fflush()库函数可强制刷新指定文件的 stdio 缓冲区;
调用 fclose()关闭文件时会自动刷新文件的 stdio 缓冲区;
程序退出时会自动刷新 stdio 缓冲区(注意区分不同的情况:return会刷新,_exit和_Exit不会刷新) 。

5.I/O缓冲小节

3.10 文件描述符和FILE指针互转

#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);

相关文章:

Linux-应用编程学习笔记(二、文件I/O、标准I/O)

一、文件I/O基础 文件 I/O 指的是对文件的输入/输出操作&#xff0c;就是对文件的读写操作。Linux 下一切皆文件。 1.1 文件描述符 在 open函数执行成功的情况下&#xff0c; 会返回一个非负整数&#xff0c; 该返回值就是一个文件描述符&#xff08;file descriptor&#x…...

AI爆文写作:根据别人的爆款标题,如何通过名词替换改成自己的爆款标题?

在日常刷到爆文的时候&#xff0c;就可以培养自己的网感&#xff0c;为啥这篇文章会爆&#xff1f; 这篇爆文的标题有啥诀窍呢&#xff1f; 比如下面这一篇&#xff1a;《极简生活&#xff1a;变富就是每天循环5个动作》 我们可以发现&#xff0c;每天循环5个动作 这几个词语…...

Mybatis源码剖析---第二讲

Mybatis源码剖析—第二讲 那我们在讲完了mappedstatement这个类&#xff0c;它的一个核心作用之后呢&#xff1f;那下面我有一个问题想问问各位。作为mappedstatement来讲&#xff0c;它封装的是一个select标签或者insert标签。但是呢&#xff0c;我们需要大家注意的是什么&am…...

SpringMvc-restful设计风格

Restful 1、入门1.1 简介1.2 实例 1、入门 1.1 简介 RESTFul是什么 RESTFul是WEB服务接口的一种设计风格。 RESTFul定义了一组约束条件和规范&#xff0c;可以让WEB服务接口更加简洁、易于理解、易于扩展、安全可靠。 1.2 实例 web.xml <?xml version"1.0"…...

在未来你将何去何从?

在数字化的浪潮中&#xff0c;信息技术行业无疑是推动全球经济和社会发展的重要动力。随着科技的不断迭代与进步&#xff0c;云计算、大数据、人工智能&#xff08;AI&#xff09;、物联网&#xff08;IoT&#xff09;、5G通信和区块链等技术已经深入到我们生活的每一个角落&am…...

Vue.js组件设计模式:构建可复用组件库

在Vue.js中&#xff0c;构建可复用的组件库是提高代码复用性和维护性的关键。下面是一些设计模式&#xff0c;说明如何创建可复用的Vue组件&#xff1a; 1. 单文件组件&#xff08;Single File Component, SFC&#xff09; Vue.js组件通常是单文件组件&#xff0c;包含HTML、…...

【C语言】指针运算

前言 前面在“走进指针世界”中我已经讲解过指针相关的很多前置知识&#xff0c;其实还有一个很重要的部分就是指针的运算。这篇博客&#xff0c;就让我们一起了解一下指针的运算吧&#xff01; 指针作为变量&#xff0c;是可以进行算术运算的&#xff0c;只不过情况会和整型…...

Python学习(3) 函数

定义 定义一个函数的格式&#xff1a; def 函数名(参数):执行代码如果没有参数&#xff0c;则称为无参函数。 定义时小括号中写的是形参&#xff08;形式参数&#xff09;&#xff0c;调用时写的是实参&#xff08;实际参数&#xff09;。 调用 调用格式&#xff1a; def…...

计算机网络安全控制技术

1.防火墙技术 防火墙技术是近年来维护网络安全最重要的手段&#xff0c;但是防火墙不是万能的&#xff0c;需要配合其他安全措施来协同 2.加密技术 目前加密技术主要有两大类&#xff1a;对称加密和非对称加密 3.用户识别技术 核心是识别网络者是否是属于系统的合法用户 …...

WordPress插件Disable WP REST API,可根据是否登录来禁用REST API

前面跟大家分享了代码版禁用WordPress REST API的方法&#xff08;详见『WordPress4.7以上版本如何禁用JSON REST API&#xff1f;』&#xff09;&#xff0c;不过有些站长不太敢折腾自己的网站代码&#xff0c;那么建议试试这款Disable WP REST API&#xff0c;它可以&#xf…...

腾讯云COS上传文件出现的问题

1、没有配置 ObjectMetadata 的文件长度 腾讯云COS上传文件出现数据损坏问题_no content length specified for stream data. strea-CSDN博客 2、 使用 FileInputStream使用完没有及时关闭导致报错 ClientAbortException: java.nio.channels.ClosedChannelException 添加…...

【C++】<知识点> 标准和文件的输入输出

目录 一、输入输出操作 1. 相关的类 2. 标准流对象 3. istream类的成员函数 二、流操纵算子 1. 整数流的基数 2. 浮点数精度的流操纵算子 3. 域宽的流操纵算子 4. 其他的流操纵算子 5. 用户自定义流操纵算子 三、文件读写 1. 文本文件的读写 2. 二进制文件的读写 3. 文件读写…...

在阿里Anolis OS 8.9龙蜥操作系统安装docker

在Anolis OS 8系统安装docker 1.更新系统 sudo dnf update -y2.安装依赖包 sudo dnf install -y yum-utils device-mapper-persistent-data lvm23.添加Docker的官方仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4.安装…...

短剧APP开发,短剧行业发展下的财富密码

今年以来&#xff0c;短剧市场展现出了繁荣发展的态势&#xff0c;成为了一个风口赛道。 短剧具有不拖沓、时长短、剧情紧凑等优势&#xff0c;顺应了当代人的生活&#xff0c;是当代人的“电子榨菜”。 短剧的快速发展同时也带动了新业态新模式的发展&#xff0c;短剧APP就是…...

简述分代垃圾回收器是怎么工作的?

分代垃圾回收器是一种用于管理和回收内存中垃圾对象的技术。它根据对象的存活时间将内存分为不同的代&#xff0c;并针对每个代应用不同的垃圾回收策略。 分代垃圾回收器的工作过程如下&#xff1a; 内存分代&#xff1a;首先&#xff0c;将内存分为不同的代&#xff0c;通常是…...

Qt 自定义代理类

一.使用步骤 继承QStyledItemDelegate类&#xff1a;首先创建一个新的类并继承自QStyledItemDelegate类&#xff0c;作为您的自定义代理类。 实现代理类的构造函数&#xff1a;在代理类中实现构造函数&#xff0c;并在构造函数中调用基类的构造函数&#xff0c;可以选择传入一…...

android GridLayout 布局详解,并举例

GridLayout 是 Android 中的一个布局容器&#xff0c;它允许你在一个二维网格中排列子视图。你可以指定网格的行数和列数&#xff0c;或者让 GridLayout 自动计算它们。每个子视图都可以占据一个或多个网格单元格。GridLayout 非常适合在需要创建规则网格的应用中使用&#xff…...

el-transfer和el-tree进行结合搞一个树形穿梭框

由于业务需求需要在穿梭框里使用树形结构&#xff0c;但是本身element里并不支持&#xff0c;于是参考了别的大佬发的文章作为思路及后续自己新增了一些处理功能。 目录 1.拷贝代码放到自己的项目目录中 2.改造el-transfer的源码 3.修改tree-transfer-panel.vue文件 4.修改…...

编一个自己的万年历

编一个自己的万年历 前阶段突然想查一下某一天是星期几&#xff0c;于是自己编了一个[小程序][https://blog.csdn.net/weixin_41905135/article/details/138972055?spm1001.2014.3001.5501]&#xff0c;但是功能很单一&#xff0c;就是单纯的查是星期几。&#xff08;虽然用网…...

Golang gin框架中间件c.JSON返回结果后终止返回

gin框架中间件c.JSON返回结果后还是会继续执行之后的方法&#xff0c;我们可以用c.Abort()来终止后续的处理 func MiddlewareFunction(c *gin.Context) {// 假设有某种条件下需要返回错误if someCondition {c.JSON(http.StatusBadRequest, gin.H{"error": "som…...

码蹄集部分题目(2024OJ赛16期;单调栈集训+差分集训)

&#x1f9c0;&#x1f9c0;&#x1f9c0;单调栈集训 &#x1f96a;单调栈 单调递增栈伪代码&#xff1a; stack<int> st; for(遍历数组) {while(栈不为空&&栈顶元素大于当前元素)//单调递减栈就是把后方判断条件变为小于等于即可{栈顶元素出栈;//同时进行其他…...

安卓玩机搞机技巧综合资源----自己手机制作证件照的几种方法 免费制作证件照

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…...

揭秘循环购模式:消费返利新玩法,引领电商新潮流

在当今的消费市场中&#xff0c;有一种商业模式引起了广大消费者的热烈讨论——那就是循环购模式。你可能会想&#xff0c;消费满千元就能得到两千元的福利&#xff0c;每天还能领取现金&#xff0c;这怎么可能呢&#xff1f;商家难道真的在“慷慨解囊”&#xff1f;今天&#…...

【制作100个unity游戏之26】unity2d横版卷轴动作类游13(附带项目源码)

最终效果 系列导航 文章目录 最终效果系列导航前言存储点灯光后处理存储位置信息存储更多数据存储场景信息持久化存储数据引入Unity 的可序列化字典类调用 游戏结束源码完结 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各…...

Golang使用HTTP框架zdpgo_resty实现文件下载

核心代码 代码解析&#xff1a; client.SetOutputDirectory("Downloads") 设置下载目录client.R().SetOutput("test.go").Get("http://127.0.0.1:3333/download 指定下载文件名并进行下载 // 设置输出目录路径&#xff0c;如果目录不存在&#xff…...

提取COCO 数据集的部分类

1.python提取COCO数据集中特定的类 安装pycocotools github地址&#xff1a;https://github.com/philferriere/cocoapi pip install githttps://github.com/philferriere/cocoapi.git#subdirectoryPythonAPI若报错&#xff0c;pip install githttps://github.com/philferriere…...

高刚性滚柱直线导轨有哪些优势?

滚柱导轨是机械传动系统中用于支持和引导滑块或导轨的装置&#xff0c;承载能力较高、刚性强及高精度等特点。特别适用于大负载和高刚性的工业设备&#xff0c;如机床、数控机床等设备&#xff0c;这些优势使其在工业生产和机械设备中得到了广泛的应用。 1、高精度&#xff1a;…...

KNN及降维预处理方法LDA|PCA|MDS

文章目录 基本原理模型介绍模型分析 python代码实现降维处理维数灾难 curse of dimensionality线性变换 Linear TransformationLDA - 线性判别分析LDA python 实现PCA - 主成分分析PCA最近重构性PCA最大可分性PCA求解及说明PCA python实现 多维缩放 Multiple Dimensional Scali…...

论文精读-SwinIR Image Restoration Using Swin Transformer

论文精读-SwinIR: Image Restoration Using Swin Transformer SwinIR:使用 Swin Transformer进行图像恢复 参数量&#xff1a;SR 11.8M、JPEG压缩伪影 11.5M、去噪 12.0M 优点&#xff1a;1、提出了新的网络结构。它采用分块设计。包括浅层特征提取&#xff1a;cnn提取&#…...

解释Spring Bean的生命周期

Spring Bean的生命周期涉及到Bean的创建、配置、使用和销毁的各个阶段。理解这个生命周期对于编写高效的Spring应用和充分利用框架的功能非常重要。下面是Spring Bean生命周期的主要步骤&#xff1a; 1. 实例化Bean Spring容器首先将使用Bean的定义&#xff08;无论是XML、注…...