APUE读书笔记-第五章 标准I/O库

2024-08-22 04:38

本文主要是介绍APUE读书笔记-第五章 标准I/O库,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天草草的把第四章结了,后面的内容分析的也不是很详细,就连书中的例子都没有怎么实验,还是等以后有机会吧。

从5.3节开始研究起吧,这一节主要谈了一个进程预定义的3个流,分别是标准输入、标准输出和标准错误,通过stdin、stdout、stderr引用。这里要和进程中的文件描述符STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO相区分。

/* 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
5.4缓冲

标准I/O提供以下3种缓冲:

  1. 全缓冲。在这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作。
  2. 行缓冲。在这种情况下,当在输入和输出遇到换行符时,标准I/O库执行I/O操作。行缓冲允许一次输出一个字符(用标准I/O函数fputc),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(如标准输入和标准输出),通常使用行缓冲。对于行缓冲又存在两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只有填满缓冲区,那么即使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它从内核请求需要数据)得到输入数据,那么就会冲洗所有行缓冲输入流。此处冲洗是指将缓冲区中的数据写入内核中。在(b)中带了一个在括号中的说明,其理由是,所需的数据可能已在该缓冲区中,它并不要求一定从内核读数据。很明显,从一个不带缓冲的流中输入(即(a)项)需要从内核获得数据。
  3. 不带缓冲。标准I/O库不对字符进行缓冲存储。

对于上面提到的第二点可通过几个简单的实验进行验证,是通过fputc向标准输出输入数据,具体程序如下:

#include <stdio.h>
#include <unistd.h>int main(void)
{char msg[] = "Hello world";int i = 0;while (msg[i]){fputc(msg[i], stdout); //将fputc函数改成printf效果相同sleep(1); //写入后程序挂起1si++;}return 0;
}

运行结果:程序先不输出,最后统一输出msg,通过这个程序基本验证了行缓冲的特点,没有换行符或写满行缓冲区的情况下,不会执行I/O操作,同时也验证了标准I/O使用行缓冲模式。但这个实验又引出了一个问题那就是进行I/O操作的时机,在以上实验中我既没有输出换行符,同时也没有写满缓冲区,暂且现把这个疑问记下,学了后面的知识也许就能解答了。

标准I/O缓冲还具有以下惯例:

  1. 标准错误是不带缓冲的。
  2. 若是指向终端设备的流,则是行缓冲的;否则是全缓冲的。

可通过以下函数更改缓冲类型。

#include <stdio.h>
extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __THROW;
extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf,int __modes, size_t __n) __THROW;

以上函数须在流已被打开后使用。同时应在对该流执行任何一个其他操作之前调用。

setvbuf的功能比较明确,使用modes参数可以设定缓冲模式:

#include <stdio.h>
#define _IOFBF 0		/* Fully buffered.  */
#define _IOLBF 1		/* Line buffered.  */
#define _IONBF 2		/* No buffering.  */

强制冲洗一个流。

#include <stdio.h>
extern int fflush (FILE *__stream);
若fp为NULL,则此函数将导致所有输出流被冲洗。

 5.5打开流

extern FILE *fopen (const char *__restrict __filename,const char *__restrict __modes) __wur;
extern FILE *freopen (const char *__restrict __filename,const char *__restrict __modes,FILE *__restrict __stream) __wur;
extern FILE *fdopen (int __fd, const char *__modes) __THROW __wur;

上述三个函数的区别如下:

  1. fopen函数打开路径名为pathname的一个指定的文件。
  2. freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准错误。
  3. fdopen函数取一个已有的文件描述符(可通过open函数获得此文件描述符),并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型的文件不能用标准I/O函数fopen打开,所以我们必须先调用设备专用函数以获得一个文件描述符,然后用fopen使一个标准I/O流与该描述符相结合。

ISO规定type参数可以有15种不同的值。

  1. r或rb:为读而打开。但文件必须已经存在。(O_RDONLY)
  2. w或wb:把文件截断至0长,或为写而创建。文件之前的内容会被删除。(O_WRONLY|O_CREAT|O_TRUNC)
  3. a或ab:追加;为在文件尾写而打开,或为写而打开。(O_WRONLY|O_CREAT|O_APPEND)
  4. r+或r+b或rb+:为读和写而打开。流只可在尾端处写。(O_RDWR)
  5. w+或w+b或wb+:把文件截断至0长,或为读和写而打开。(O_RDWR|O_CREAT|O_TRUNC)
  6. a+或a+b或ab+:为在文件尾读和写而打开或创建。(O_RDWR|O_CREAT|O_APPEND)

有关于type解释请见:http://www.cnblogs.com/emanlee/p/4418163.html

由于内核不区分文本文件和二进制文件。所以b作为type的一部分实际上并无作用。

对于fdopen函数,若描述符已被打开,则fdopen为写而打开并不截断该文件。另外,标准I/O追加写方式(a或a+)也不能用于创建该文件(因为如果一个描述符引用一个文件,则该文件一定已经存在)。

在使用w或a类型创建一个新文件时,POSIX.1要求使用如下权限来创建文件:

S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
可通过unmask值来更改权限。

按照系统默认,流打开时是全缓冲的。若流引用终端设备,则该设备是行缓冲的。

调用fclose可关闭一个打开的流。

#include <stdio.h>
extern int fclose (FILE *__stream);
在该文件被关闭前,冲洗缓冲中的输出数据。缓冲区中的任何输入数据被丢弃。

当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。这也解释了我们之前留下的疑问,缓冲区中数据冲洗的时机——进程正常终止时,根据我们之前学习到的知识,应该是在main函数开始之前注册了某个析构函数,这个析构函数的功能就是冲洗缓冲区。


5.6 读和写流

标准I/O提供三种方式进行非格式化读、写操作。

  1. 每次一个字符的I/O。
  2. 每次一行的I/O。每行都以一个换行符终止。
  3. 直接I/O。

每次一个字符的I/O。由于标准I/O采用行缓冲模式,所以只有出现换行符或写满一行的情况下才会冲洗缓冲区,此时数据已经写入缓冲区中。输入函数如下:

#include <stdio.h>
extern int fgetc (FILE *__stream);
extern int getc (FILE *__stream);
extern int getchar (void); 函数getchar()等同于getc(stdin)
getc的具体实现为:

#include <stdio.h>
#define getc(_fp) _IO_getc (_fp)
使用int作为返回值的原因是:返回所有可能的字符值在加上一个已出错或已达到文件尾端的指示值。我也查了以下仅是ascii码表就已经包含128个符号,因此使用char作为返回数据远远不够。

EOF的定义为

#include <stdio.h>
#ifndef EOF
# define EOF (-1)
#endif
对于到达文件尾端或出错都会返回EOF,所以为了进一步区分这两种情况,引入两个函数:

#include <stdio.h>
extern int feof (FILE *__stream) __THROW __wur; //若以达到文件尾端,则返回非0,否则返回0
extern int ferror (FILE *__stream) __THROW __wur; //若输入出错,则返回非0,否则返回0

为每个流在FILE对象中维护了两个标志。

  1. 出错标志。
  2. 文件结束标志。

由于feof仅检查文件结束标志位是否被置位,文件结束标志是由当前时间之前的最后一次相关操作(包括读、seek等操作)设置的。因此在fgets函数前调用feof还无法判断文件流是否到达尾部。有关于这个问题的解决方法请见:http://book.51cto.com/art/201311/419432.htm

调用clearerr可以清除这两个标志。

#include <stdio.h>
extern void clearerr (FILE *__stream) __THROW;

从流中读取数据后,可以调用ungetc将字符再压送回流中。

#include <stdio.h>
extern int ungetc (int __c, FILE *__stream);
每次一个字符的I/O。输出函数:

#include <stdio.h>
extern int fputc (int __c, FILE *__stream);
extern int putc (int __c, FILE *__stream);
#define putc(_ch, _fp) _IO_putc (_ch, _fp)
extern int putchar (int __c);

5.7 每次一行I/O

5.6节主要分析每次一个字符的I/O,5.7节则介绍每次一行的I/O。

先来看输入函数:

#include <stdio.h>
extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream) //从指定流读__wur;
extern char *gets (char *__s) __wur __attribute_deprecated__; //从标准输入读

由于是标准I/O,所以一直读到下一个换行符为止,但不会超过n-1个字符。该缓冲区以null字节结尾。如若该行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字节结尾。对fgets的下一次调用会继续读该行。

先来看看对fgets的下一次调用会继续读改行这一点,通过一个验证一下。源码如下:

#include <stdio.h>
#include <stdlib.h>int main()
{FILE* fp;char* buf1 = (char*)malloc(4*sizeof(char));char* buf2 = (char*)malloc(7*sizeof(char));fp = fopen("./temp","r");fgets(buf1,4,fp);printf("%s\n",buf1);fgets(buf2,7,fp);	printf("%s\n",buf2);free(buf1);free(buf2);return 0;
}

temp文件中的内容还是我们熟悉的“hello world”

运行结果如下:

hel
lo wor

根据我们之前学到的知识,fgets在没有读到换行符的情况下,会读取n-1个字符,buf1的长度为4,所以会读取三个字符,也就是“hel”,输出第一行也印证了这一知识点。

由于标准I/O使用行缓冲模式,根据实验的结果,我猜测fgets会一次性读取一定长度的数据填充到缓冲区中,每次调用fgets函数会从缓冲区中读取n-1个字符,直到数据被读取完。但如果数据中包含有换行符,则执行I/O操作,此处就是输出到屏幕。

由于缓冲区中的数据没有被全部读取,因此fgets会继续读该行。输出的第二行也验证了这一点。

或者不从源码的角度理解,仅从功能角度理解函数的特点,fgets一次就是读取一行,如果这其中包含有换行符,实际上就意味着数据不是一行而是两行。读取一行数据后,fgets将n-1个字符复制到用户缓冲区中,知道缓冲区中的数据被全部读取完。根据以上描述,我们自己都可以实现一个简单的fgets函数。函数的执行流程如下:

  1. 判断当前位置指针是否为0,若为0则直接跳转到4执行,否则顺序执行。
  2. 读取数据,若遇到"\n"则跳出循环,否则一直读取直到缓冲区满。
  3. 首先设置当前位置指针指向缓冲区的头部。
  4. 计算复制的字符数,若缓冲区中包含有足够的数据则复制用户指定字节数的数据,否则复制缓冲区中实际含有的数据。
  5. 将用户缓冲区的最后一位置为"\0"。
  6. 更新当前位置指针。

fgets函数的源码在此就不详细分析了,争取对fread的源码进行一个简单的分析。

输出函数,每次一行。

#include <stdio.h>
extern int fputs (const char *__restrict __s, FILE *__restrict __stream);
extern int puts (const char *__s); 

puts输出后还会将一个换行符写到标准输出。

5.9二进制I/O

二进制I/O的功能主要就是读入或写入任意类型、任意字节的数据,其中的两个参数与MPI接口中包含的参数相类似。

函数原型类型:

#include <stdio.h>
extern size_t fread (void *__restrict __ptr, size_t __size,size_t __n, FILE *__restrict __stream) __wur;
extern size_t fwrite (const void *__restrict __ptr, size_t __size,size_t __n, FILE *__restrict __s);
返回读或写的对象数。对于写,如果返回值小于所要求的__n,则出错。可通过ferror检查。

5.12 实现细节

通过以下函数可以获得文件流指针对应的文件描述符。

#include <stdio.h>
extern int fileno (FILE *__stream) __THROW __wur;

有关于FILE结构的定义如下,位于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 0int _blksize;
#elseint _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
};

有关于临时文件与内存流的内容,现在就先不跟大家分享了,以后遇到的时候在详细研究吧。

关于书后习题第6题给大家分享一点我的见解

题目如下:打印的提示信息没有包含换行符,程序也没有调用fflush函数,请解释输出提示信息的原因是什么?

题目中给出了两点条件,逐条来分析。

“打印的提示信息没有包含换行符”,由于标准I/O使用行缓冲模式,所以没有换行符则不执行I/O操作。此处就是不输出提示信息。

“程序也没有调用fflush函数”,fflush函数的功能就是将流所有未写的数据都传送至内核。作为一种特殊情形,如若fp为NULL,则此函数将导致所有输出流被清洗。此处就是不输出提示信息。

以上两点的结果都是不输出提示信息,那么是什么原因导致提示信息的输出?

这是基于行缓冲的特点:从一个行缓冲的流得到输入数据,输出流就会被自动冲洗。此处就是每次调用fgets时标准输出设备将自动冲洗。

关于以上内容我们可以通过几个简单的实验进行验证。首先来看一个永远不会输出的源码:

#include <stdio.h>int main()
{char output[] = "Hello world";printf("%s",output);while(1);return 0;
}

调用printf后,程序进入死循环,通过之前的知识可以了解到一个进程正常终止时,所有带未写缓冲数据的标准I/O流都被冲洗。由于此处进程一直未能正常终止,所以信息一直未能输出。

针对第一点做一点调整:

#include <stdio.h>int main()
{char output[] = "Hello world\n";printf("%s",output);while(1);return 0;
}

此时程序可以正常输出,或者进行相同的调整:

#include <stdio.h>int main()
{char output[] = "Hello world";printf("%s\n",output);while(1);return 0;
}

好了看完第一点,针对第二点作出调整:

#include <stdio.h>int main()
{char output[] = "Hello world";printf("%s",output);fflush(stdout);while(1);return 0;
}

最后一种调整方法:

#include <stdio.h>int main()
{char output[] = "Hello world";printf("%s",output);fgetc(stdin);while(1);return 0;
}

在标准输出后进行标准输入。






这篇关于APUE读书笔记-第五章 标准I/O库的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1095266

相关文章

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

C 标准库 - `<float.h>`

C 标准库 - <float.h> 概述 <float.h> 是 C 标准库中的一个头文件,它定义了与浮点数类型相关的宏。这些宏提供了关于浮点数的属性信息,如精度、最小和最大值、以及舍入误差等。这个头文件对于需要精确控制浮点数行为的程序非常有用,尤其是在数值计算和科学计算领域。 主要宏 <float.h> 中定义了许多宏,下面列举了一些主要的宏: FLT_RADIX:定义了浮点数的基数。

《C++标准库》读书笔记/第一天(C++新特性(1))

C++11新特性(1) 以auto完成类型自动推导 auto i=42; //以auto声明的变量,其类型会根据其初值被自动推倒出来,因此一定需要一个初始化操作; static auto a=0.19;//可以用额外限定符修饰 vector<string> v;  auto pos=v.begin();//如果类型很长或类型表达式复杂 auto很有用; auto l=[] (int

读书笔记(一):双脑记

谁又知道年轻人那反复无常的大脑有着怎样的运行机制?尽管他们的大脑已被荷尔蒙折腾地七荤八素;却偶尔还会有灵感跻身夹缝之间; 层级化:每时每刻,人类都在进行抽象化,也就是说,从客观事实中发展出更具普遍意义的理论和知识。利用这种方法,我们得以不断地开发出新的更为简洁的描述层级,方便我们那容量有限的大脑加以处理。分层的概念几乎可以应用于任何复杂系统,甚至包括我们的社交世界,也即是人们的个人生

React第五章(swc)

swc 什么是swc? SWC 既可用于编译,也可用于打包。对于编译,它使用现代 JavaScript 功能获取 JavaScript / TypeScript 文件并输出所有主流浏览器支持的有效代码。 SWC在单线程上比 Babel 快 20 倍,在四核上快 70 倍。 简单点来说swc实现了和babel一样的功能,但是它比babel快。 FAQ为什么快? 编译型 Rust 是

标准IO与系统IO

概念区别 标准IO:(libc提供) fopen fread fwrite 系统IO:(linux系统提供) open read write 操作效率 因为内存与磁盘的执行效率不同 系统IO: 把数据从内存直接写到磁盘上 标准IO: 数据写到缓存,再刷写到磁盘上

通信工程学习:什么是AM标准调幅

AM标准调幅       AM标准调幅,即Amplitude Modulation(振幅调制),是一种在电子通信中广泛使用的调制方法,特别是在无线电载波传输信息方面。以下是关于AM标准调幅的详细解释: 一、AM标准调幅的定义与原理 AM标准调幅的定义:        AM调幅是通过改变载波信号的振幅(即信号强度或电压幅度),使其与信息信号(如音频、视频等)同步变化,从而实现信息的传

【电子通识】洁净度等级划分及等级标准

洁净度常用于评估半导体、生物制药、医疗、实验室及科研院所、新能源等领域的洁净室、无尘室或者无菌室等环境。         一般来说,晶圆光刻、制造、测试等级为100级或1000级的洁净间,百级洁净间要求空气中0.5微米的尘埃粒子数不得超过每立方米3520个;等级为1000级的洁净间要求0.5微米的尘埃粒子数不得超过每立方米35200个。         晶圆切割或封装工序一

标准库标头 <filesystem> (C++17)学习

此头文件是文件系统支持库的一部分。本篇介绍filesystem命名空间的一些函数。 函数 在命名空间 std::filesystem 定义 absolute (C++17) 组成一个绝对路径 (函数) canonicalweakly_canonical (C++17) 组成一个规范路径 (函数) relativeproximate (C++17) 组成一个相对路径 (函数) copy (C

2024.09.07【读书笔记】| SMRTLink工具对PB组装疑难解答

在使用SMRT Link的pb_assembly_hifi命令进行组装分析时,可以参考以下步骤和信息: 使用pbcromwell show-workflow-details pb_assembly_hifi命令查看该工作流的详细信息。这将帮助你了解所需的输入参数和可选输入参数。 根据工作流的要求,你需要准备相应的输入文件。例如,对于单样本基因组组装,需要CCS(连续测序)的fastq文件路径作