Unix环境高级编程003.pdf
《Unix环境高级编程003.pdf》由会员分享,可在线阅读,更多相关《Unix环境高级编程003.pdf(50页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、表5-4 使用标准I/O例程得到的时间结果函数用户C P U(秒)系统C P U(秒)时钟时间(秒)程序正文字节数表3-1中的最佳时间0.00.30.3f g e t s,f p u t s2.20.32.61 8 4g e t c,p u t c4.30.34.83 8 4f g e t c,f p u t c4.60.35.01 5 2表3-1中的单字节时间2 3.83 9 7.94 2 3.4对于这三个标准I/O版本的每一个,其用户C P U时间都大于表3-1中的最佳r e a d版本,因为每次读一个字符版本中有一个要执行 1 5 0万次的循环,而在每次读一行的版本中有一个要执行30 0
2、00次的循环。在r e a d版本中,其循环只需执行1 8 0次(对于缓存长度为8 1 9 2字节)。因为系统C P U时间都相同,所以用户C P U时间的差别造成了时钟时间的差别。系统C P U时间相同的原因是因为所有这些程序对内核提出的读、写请求数相同。注意,使用标准I/O例程的一个优点是无需考虑缓存及最佳 I/O长度的选择。在使用f g e t s时需要考虑最大行长,但是最佳I/O长度的选择要方便得多。表5-4中的最后一列是每个m a i n函数的文本空间字节数(由 C编译产生的机器指令)。从中可见,使用g e t c的版本在文本空间中作了 g e t c和p u t c的宏代换,所以它
3、所需使用的指令数超过了调用f g e t c和f p u t c函数所需指令数。观察g e t c版本和f g e t c版本在用户C P U时间方面的差别,可以看到在程序中作宏代换和调用两个函数在进行本测试的系统上并没有造成多大差别。使用每次一行I/O版本其速度大约是每次一个字符版本的两倍(包括用户 C P U时间和时钟时间)。如果f g e t s和f p u t s函数用g e t c和p u t c实现(例如,见K e r n i g h a n和R i t c h i e1 9 8 8的7.7节),那么,可以预期f g e t s版本的时间会与g e t c版本相接近。实际上,可以预
4、料每次一行的版本会更慢一些,因为除了现已存在的 60 000次函数调用外还需增加3百万次宏调用。而在本测试中所用的每次一行函数是用m e m c c p y(3)实现的。通常,为了提高效率,m e m c c p y函数用汇编语言而非C语言编写。这些时间数字的最后一个有趣之处在于:f g e t c版本较表3-1中B U F F S I Z E1的版本要快得多。两者都使用了约3百万次的函数调用,而f g e t c版本的速度在用户C P U时间方面,大约是后者的5倍,而在时钟时间方面则几乎是1 0 0倍。造成这种差别的原因是:使用r e a d的版本执行了3百万次函数调用,这也就引起 3百万次
5、系统调用。而对于f g e t c版本,它也执行3百万次函数调用,但是这只引起3 6 0次系统调用。系统调用与普通的函数调用相比是很花费时间的。需要声明的是这些时间结果只在某些系统上才有效。这种时间结果依赖于很多实现的特征,而这种特征对于不同的 U N I X系统却可能是不同的。尽管如此,使这样一组数据,并对各种版本的差别作出解释,这有助于我们更好地了解系统。在本节及3.9节中我们学到的基本事实是:标准I/O库与直接调用r e a d和w r i t e函数相比并不慢很多。我们观察到使用g e t c和p u t c复制1 M字节数据大约需3.0秒C P U时间。对于大多数比较复杂的应用程序,
6、最主要的用户C P U时间是由应用本身的各种处理花费的,而不是由标准I/O例程消耗的。5.9 二进制I/O5.6节中的函数以一次一个字符或一次一行的方式进行操作。如果为二进制 I/O,那么我们更愿意一次读或写整个结构。为了使用 g e t c或p u t c做到这一点,必须循环通过整个结构,一次1 0 0U N I X环境高级编程下载读或写一个字节。因为 f p u t s在遇到n u l l字节时就停止,而在结构中可能含有 n u l l字节,所以不能使用每次一行函数实现这种要求。相类似,如果输入数据中包含有n u l l字节或新行符,则f g e t s也不能正确工作。因此,提供了下列两个
7、函数以执行二进制 I/O操作。#include size_t fread(void*p t r,size_t s i z e,size_t n o b j,FILE*f p);size_t fwrite(const void*p t r,size_t s i z e,size_t n o b j,FILE*f p);两个函数的返回:读或写的对象数这些函数有两个常见的用法:(1)读或写一个二进制数组。例如,将一个浮点数组的第 2至第5个元素写至一个文件上,可以写作:float data 1 0;if(fwrite(&data2,sizeof(float),4,fp)!=4)err_sys(fwr
8、ite error);其中,指定s i z e为每个数组元素的长度,n o b j为欲写的元素数。(2)读或写一个结构。例如,可以写作:struct short count;long total;char name N A M E S I Z E;item;if(fwrite(&item,sizeof(item),1,fp)!=1)err_sys(fwrite error);其中,指定s i z e为结构的长度,n o b j为1(要写的对象数)。将这两个例子结合起来就可读或写一个结构数组。为了做到这一点,s i z e应当是该结构的s i z e o f,n o b j应是该数组中的元素数。
9、f r e a d和f w r i t e返回读或写的对象数。对于读,如果出错或到达文件尾端,则此数字可以少于n o b j。在这种情况,应调用f e r r o r或f e o f以判断究竟是那一种情况。对于写,如果返回值少于所要求的n o b j,则出错。使用二进制I/O的基本问题是,它只能用于读已写在同一系统上的数据。多年之前,这并无问题(那时,所有 U N I X系统都运行于P D P-11上),而现在,很多异构系统通过网络相互连接起来,而且,这种情况已经非常普遍。常常有这种情形,在一个系统上写的数据,在另一个系统上处理。在这种环境下,这两个函数可能就不能正常工作,其原因是:(1)在一
10、个结构中,同一成员的位移量可能随编译程序和系统的不同而异(由于不同的对准要求)。确实,某些编译程序有一选择项,它允许紧密包装结构(节省存储空间,而运行性能则可能有所下降)或准确对齐,以便在运行时易于存取结构中的各成员。这意味着即使在单一系统上,一个结构的二进制存放方式也可能因编译程序的选择项而不同。(2)用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也可能不同。在不同系统之间交换二进制数据的实际解决方法是使用较高层次的协议。关于网络协议使用的交换二进制数据的某些技术,请参阅S t e v e n s1 9 9 0的1 8.2节。第5章标 准 I/O 库1 0 1下载在8.1 3节中,
11、我们将再回到 f r e a d函数,那时将用它读一个二进制结构U N I X的进程记账记录。5.10 定位流有两种方法定位标准I/O流。(1)ftell和f s e e k。这两个函数自V 7以来就存在了,但是它们都假定文件的位置可以存放在一个长整型中。(2)fgetpos和f s e t p o s。这两个函数是新由ANSI C引入的。它们引进了一个新的抽象数据类型f p o s _ t,它记录文件的位置。在非U N I X系统中,这种数据类型可以定义为记录一个文件的位置所需的长度。需要移植到非U N I X系统上运行的应用程序应当使用f g e t p o s和f s e t p o s
12、。#include long ftell(FILE*f p);返回:若成功则为当前文件位置指示,若出错则为 1 Lint fseek(FILE*f p,long o f f s e t,int w h e n c e);返回:若成功则为0,若出错则为非0void rewind(FILE*f p);对于一个二进制文件,其位置指示器是从文件起始位置开始度量,并以字节为计量单位的。f t e l l用于二进制文件时,其返回值就是这种字节位置。为了用 f s e e k定位一个二进制文件,必须指定一个字节 o f f s e t,以及解释这种位移量的方式。w h e n c e的值与3.6节中l s
13、e e k函数的相同:S E E K _ S E T表示从文件的起始位置开始,S E E K _ C U R表示从当前文件位置,S E E K _ E N D表示从文件的尾端。ANSI C并不要求一个实现对二进制文件支持 S E E K _ E N D规格说明,其原因是某些系统要求二进制文件的长度是某个幻数的整数倍,非实际内容部分则充填为 0。但是在U N I X中,对于二进制文件S E E K _ E N D是得到支持的。对于文本文件,它们的文件当前位置可能不以简单的字节位移量来度量。再一次,这主要也是在非 U N I X系统中,它们可能以不同的格式存放文本文件。为了定位一个文本文件,w h
14、 e n c e一定要是S E E K _ S E T,而且o f f s e t只能有两种值:0(表示反绕文件至其起始位置),或是对该文件的f t e l l所返回的值。使用r e w i n d函数也可将一个流设置到文件的起始位置。正如我们已提及的,下列两个函数是C标准新引进的。#include int fgetpos(FILE*f p,fpos_t*p o s);int fsetpos(FILE*f p,const fpos_t*p o s);两个函数返回:若成功则为0,若出错则为非0f g e t p o s将文件位置指示器的当前值存入由 p o s指向的对象中。在以后调用 f s e
15、 t p o s时,可以使用此值将流重新定位至该位置。1 0 2U N I X环境高级编程下载5.11 格式化I/O5.11.1 格式化输出执行格式化输出处理的是三个p r i n t f函数。#include int printf(const char*f o r m a t,.);int fprintf(FILE*f p,const char*f o r m a t,.);两个函数返回:若成功则为输出字符数,若输出出错则为负值int sprintf(char*b u f,const char*f o r m a t,.);返回:存入数组的字符数p r i n t f将格式化数据写到标准输出
16、,f p r i n t f写至指定的流,s p r i n t f将格式化的字符送入数组b u f中。s p r i n t f在该数组的尾端自动加一个n u l l字节,但该字节不包括在返回值中。4.3 B S D定义s p r i n t f返回其第一个参数(缓存指针,类型为 c h a r*),而不是一个整型。ANSI C要求s p r i n t f返回一个整型。注意,s p r i n t f可能会造成由b u f指向的缓存的溢出。保证该缓存有足够长度是调用者的责任。对这三个函数可能使用的各种格式变换,请参阅U N I X手册,或K e r n i g h a n和R i t c
17、h i e1 9 8 8的附录B。下列三种p r i n t f族的变体类似于上面的三种,但是可变参数表(.)代换成了a rg。#i n c l u d e#i n c l u d e int vprintf(const char*f o r m a t,va_list a rg);int vfprintf(FILE*f p,const char*f o r m a t,va_list a rg);两个函数返回:若成功则为输出字符数,若输出出错则为负值int vsprintf(char*b u f,const char*f o r m a t,va_list a rg);返回:存入数组的字符数
18、在附录B的出错例程中,将使用v s p r i n t f函数。关于ANSI C标准中有关可变长度参数表的详细说明请参阅 K e r n i g h a n和R i t c h i e1 9 8 8的7.3节。应当了解的是,由ANSI C提供的可变长度参数表例程(头文件和相关的例程)与由S V R 3(以及更早版本)和4.3 B S D提供的例程是不同的。5.11.2 格式化输入执行格式化输入处理的是三个s c a n f函数。第5章标 准 I/O 库1 0 3下载#i n c l u d e int scanf(const char*f o r m a t,.);int fscanf(FIL
19、E*f p,const char*f o r m a t,.);int sscanf(const char*b u f,const char*f o r m a t,.);三个函数返回:指定的输入项数,若输入出错,或在任意变换前已至文件尾端则为 E O F如同p r i n t f族一样,关于这三个函数的各个格式选择项的详细情况,请参阅 U N I X手册。5.12 实现细节正如前述,在U N I X中,标准I/O库最终都要调用第3章中说明的I/O例程。每个I/O流都有一个与其相关联的文件描述符,可以对一个流调用f i l e n o以获得其描述符。#include int fileno(FI
20、LE*f p);返回:与该流相关联的文件描述符如果要调用d u p或f c n t l等函数,则需要此函数。为了了解你所使用的系统中标准 I/O库的实现,最好从头文件 开始。从中可以看到:F I L E对象是如何定义的,每个流标志的定义,定义为宏的各个标准 I/O例程(例如g e t c)。K e r n i g h a n和R i t c h i e1 9 8 8中的8.5节含有一个简单的实现,从中可以看到很多U N I X实现的基本样式。P l a u g e r1 9 9 2的第1 2章提供了标准I/O库一种实现的全部源代码。4.3B S D中标准I/O库的实现(由Chris To r
21、e k编写)也是可以公开使用的。实例程序5-3为三个标准流以及一个与一个普通文件相关联的流打印有关缓存状态信息。注意,在打印缓存状态信息之前,先对每个流执行 I/O操作,因为第一个I/O操作通常就造成为该流分配缓存。结构成员_ f l a g、_ b u f s i z以及常数_ I O N B F和_ I O L B F是由作者所使用的系统定义的。如果运行程序5-3两次,一次使三个标准流与终端相连接,另一次使它们重定向到普通文件,则所得结果是:$a.o u tstdin,stdout 和s t d e rr 都连至终端enter any character键入新行符one line to s
22、tandard errorstream=stdin,line buffered,buffer size=128stream=stdout,line buffered,buffer size=128stream=stderr,unbuffered,buffer size=8stream=/etc/motd,fully buffered,buffer size=8192$a.out std.out 2 std.err三个流都重定向,再次运行该程序$cat std.errone line to standard error$cat std.outenter any characterstream=s
23、tdin,fully buffered,buffer size=81921 0 4U N I X环境高级编程下载stream=stdout,fully buffered,buffer size=8192stream=stderr,unbuffered,buffer size=8stream=/etc/motd,fully buffered,buffer size=8192程序5-3 对各个标准I/O流打印缓存状态信息从中可见,该系统的默认是:当标准输入、输出连至终端时,它们是行缓存的。行缓存的长度是1 2 8字节。注意,这并没有将输入、输出的行长限制为 1 2 8字节,这只是缓存的长度。如果要
24、将5 1 2字节的行写到标准输出则要进行四次 w r i t e系统调用。当将这两个流重新定向到普通文件时,它们就变成是全缓存的,其缓存长度是该文件系统优先选用的 I/O长度(从s t a t结构中得到的s t _ b l k s i z e)。从中也可看到,标准出错如它所应该的那样是非缓存的,而普通文件按系统默认是全缓存的。5.13 临时文件标准I/O库提供了两个函数以帮助创建临时文件。#i n c l u d e char*tmpnam(char*p t r);返回:指向一唯一路径名的指针第5章标 准 I/O 库1 0 5下载FILE*tmpfile(void);返回:若成功则为文件指针,
25、若出错则为 N U L Lt m p n a m产生一个与现在文件名不同的一个有效路径名字符串。每次调用它时,它都产生一个不同的路径名,最多调用次数是T M P _ M A X。T M P _ M A X定义在中。虽然T M P _ M A X是由ANSI C定义的。但该C标准只要求其值至少应为2 5。但是,X P G 3却要求其值至少为10 000。在此最小值允许一个实现使用 4位数字作为临时文件名的同时(0 0 0 0 9 9 9 9),大多数U N I X实现使用的却是大、小写字符。若p t r是N U L L,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。下一次
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Unix 环境 高级 编程 003
限制150内