UNIX 环境高级编程第五章

流和FILE对象

         在第三章中,所有I/O函数都是针对文件描述符的。当打开一个文件时,即返回一个文件描述符,然后该文件描述符就用于后续的I/O操作。而对于标准I/O库,他们的操作是围绕流(stream)进行的。当用标准I/O库打开或创建一个文件时,我们已使一个流与一个文件相关联。对于ASCII字符集,一个字符用一个字节表示。对于国际字符集,一个字符可用多个字节表示。标准I/O文件流可用于单字节或多字节("宽")字符集。流的定向决定了所读,写的字符是单字节还是多字节的。当一个流最初被创建时,它并没有定向。如若在未定向的流上使用一个多字节I/O函数(wchar.h),则将该留的定向设置为宽定向的。若在未定向的流上使用一个单字节I/O函数,则将该流的定向设置为字节定向的。只有两个函数可以改变流的定向。freopen函数清除一个流的定向;fwide函数设置流的定向。

#include<stdio.h>
#include<wchar.h>

int fwide(FILE *stream,int mode);
//fwide函数是C标准库中的一个函数,主要用于确定当前宽度模式(宽字符模式或窄字符模式),或根据需要更改流的宽度模式。
stream:是指向文件流的指针。
mode:是一个整数,用于设置或检查宽度模式,它的值可以是以下三种之一:
	如果mode>0,fwide将试图使指定的流是宽字符模式
    如果mode<0,fwide将试图使指定的流是字节定向的
    如果mode==0,则不更改流的宽度模式,只返回当前模式
注意,fwide并不改变已定向流的定向。还应注意的是,fwide无出错返回。

        当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理该流所需要的所有信息。包括:用于实际I/O的文件描述符,指向用于该缓冲区的指针,缓冲区的长度,当前在缓冲区中的字符数以及出错标志等等。

缓冲

        标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。它也对每个I/O流自动的进行缓冲管理。从而避免了应用程序需要考虑这一点所带来的麻烦。

标准I/O提供了三种类型的缓冲:

        (1)全缓冲:这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。

        术语冲洗(flush)说明标准I/O缓冲区的写操作。缓冲区可由标准I/O例程自动冲洗(例如当填满一个缓冲区时),或者可以调用函数fflush冲洗一个流。在UNIX环境中,flush有两种意思。在标准I/O库方面flush(冲洗)意味着将缓冲区中的内容写到磁盘上(该缓冲区可能只是局部填写的)。在中断驱动程序方面,flush(刷清)表示丢弃已存储在缓冲区中的数据。

        (2)行缓冲:在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/Ofputc函数),但只有在写了一行之后才进行实际I/O操作。当流设计一个终端时(例如标准输入和标准输出),通常使用行缓冲。

        对于行缓冲有两个限制,第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲的输出流。

        (3)不带缓冲:标准I/O库不对字符进行缓冲存储。例如,如果用标准I/O函数fputs写15个字符到不带缓冲的一个流中,则该函数很可能用write系统调用函数将这些字符立即写至相关联的打开文件上。

        标准出错流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管他们是否含有一个换行符。

#include<stdio.h>

void setbuf(FILE *stream,char *buf);
//用于设置文件流的缓冲区
//可以使用setbuf函数打开或关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区(该常量定义在<stdio.h>中)。通常在此之后该流就是全缓冲的,但是如果该流与一个终端设备相关,那么某些系统也可将其设置为行缓冲。为了关闭缓冲,将buf设置为NULL
stream:是指向文件流的指针
buf:是指向用户提供的缓冲区的指针。如果buf为NULL,则禁用缓冲,使流不缓冲
使用setbuf可以在文件流打开之后立即设置其缓冲区类型。
    
int setvbuf(FILE *stream,char *buf,int mode,size_t size);
//用于设置文件流的缓冲模式和缓冲机制
//如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或行缓冲,则buf和size可选择指定一个缓冲区机器长度。如果该流是带缓冲的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZ所指定的值。
stream:是指向文件流的指针
buf:是指向用户提供的缓冲区的指针。如果buf为NULL,则系统自动分配缓冲区
mode:指定缓冲模式,可以是以下之一:
	_IOFBF:全缓冲
    _IOLBF:行缓冲
    _IONBF:无缓冲
size:是缓冲区的大小
返回值:
	如果调用成功,返回0
    如果调用失败,返回非零值

#include<stdio.h>

int fflush(FILE *stream);
//用于刷新输出缓冲区,它将缓冲区中的数据强制写入文件中。
stream:是指向文件流的指针
    如果stream是一个输出流,则fflush会将该流的输出缓冲区内容写入文件
    如果stream是NULL,fflush或刷新所有打开的流
返回值:
    如果成功,返回0
    如果失败,返回EOF,并设置错误指示符

 

打开流

        下列三个函数打开一个标准流

#include<stdio.h>

FILE *fopen(const char *filename,const char *mode);
//用于打开一个文件并返回一个文件流指针,如果文件无法打开,则返回NULL
filename:是要打开的文件的名称
mode是文件打开模式,可以是以下之一:
	"r":以只读方式打开文件。
	"w":以写入方式打开文件。如果文件存在,则清空文件;如果文件不存在,则创建新文件。
	"a":以追加方式打开文件。文件指针定位在文件末尾。如果文件不存在,则创建新文件。
	"r+":以读写方式打开文件。文件必须存在。
	"w+":以读写方式打开文件。如果文件存在,则清空文件;如果文件不存在,则创建新文件。
	"a+":以读写方式打开文件。文件指针定位在文件末尾。如果文件不存在,则创建新文件。
        
FILE *freopen(const char *filename,const char *mode,FILE *stream);
//用于重新打开一个文件,并将指定的文件流与新文件关联。通常用于重定向标准输入,输出或错误流
filename:是要打开的文件的名称
mode:是文件的额打开模式
stream:是要重新打开的文件流
    
    
FILE *fdopen(int fd,const char *mode);
//用于将一个已有的文件描述符转换为一个文件流指针。
fd:是文件描述符
mode:是文件的打开模式,与fopen的模式相同

这三个函数的区别是:

        (1)fopen打开一个指定的文件

        (2)freopen在一个指定的流上打开一个指定的文件,如果该流已经打开,则先关闭该流。若该流已经定向,则freopen清除该定向。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入,标准输出或标准出错。

        (3)fdopen获取一个现有的文件描述符,并使一个标准的I/O流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型的文件不能用标准I/Ofopen函数打开,所以我们不需小调用设备专用函数以获得一个文件描述符,然后用fdopen使一个标准I/O流与百描述符相关联。

 

        使用字符b作为mode的一部分,这使得标准I/O系统可以区分文本文件和二进制文件。因为UNIX内核并不对这两种文件进行区分,所以在UNIX系统环境下指定字符b作为mode的一部分实际上并无作用。

        对于fdopen,mode参数的意义稍有区别。因为该描述符已被打开,所以fdopen为写而打开并不截短该文件。(例如,若该描述符原来是由open函数创建的,而且该文件那时已经存在,则其O_TRUNC标志将决定是否截短该文件。fdopen函数不能截短它为写而打开的任一文件。)另外,标准I/O添写方式也不能用于创建该文件(因为如弱一个描述如引用一个文件,则该文件一定已经存在)。

        当用添写类型打开一文件后,则每次写都将数据写到文件的当前尾端处。如若有多个进程用标准I/O添写方式打开了同一文件,那么来自每个进程的数据都将正确的写到文件中。

当以读和写类型打开一个文件时(mode中+符号),具有下列限制:

        (1)如果中间没有fflush,fseek,fsetpos或rewind,则在输出的后面不能直接跟随输入。(如果你在一个文件上执行了写操作,例如fprintf或fwrite,然后想要在这个文件上执行读操作,例如,使用fscanf或fread,你需要限制性一个操作来刷新输出缓冲区或者移动文件位置指针。这是因为写操作可能将数据放入了缓冲区,但这些数据还没有被实际写入文件。如果你不刷新缓冲区,直接尝试读取,可能会读取到旧的数据而不是你刚刚写入的数据)。

        (2)如果中间没有fseek,fsetpos或rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。(如果你在一个文件上执行了读操作,然后想要在这个文件上执行写操作,你需要先执行一个操作来移动文件位置指针。这是因为读操作可能已经将文件位置指针移动到了文件的某个位置,如果你不移动指针,直接尝试写入,可能会覆盖掉你刚刚读取的数据。此外,如果你读操作没有到达文件尾端,那么写操作可能会在文件的中间开始写入,而不是在文件的末尾追加数据)。

          除非流引用终端设备,否则按系统默认的情况,流被打开时是全缓冲的。若流引用终端设备,则该流式行缓冲的。一旦打开了流,那么在对该流执行任何操作之前,如果希望,则可使用setbuf和setvbuf改变缓冲的类型。

#include<stdio.h>

int fopen(FILE *fp);
//关闭一个打开的流
//在该文件被关闭之前,冲洗缓冲区中的输出数据。丢弃缓冲区中的任何输入数据。如果标准I/O库已经为该流自动分配了一个缓冲区,则释放该缓冲区。当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓冲数据的标准I/O流都会被冲洗,所有打开的标准I/O流都会被关闭。
读和写流

        

​    一旦打开了流,则可在三种不同类型的非格式化I/O中进行选择,对其进行读,写操作:

​    (1)每次一个字符的I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数会处理所有缓冲。

​    (2)每次一行的I/O。如果想要一次读或写一行,则使用fgets和fputs。每行都以一个换行符终止。当调用fgets时,应说明能处理的最大行长。

​    (3)直接I/O。fread和fwrite函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,而每个对象具有指定的长度。这两个函数常用于从二进制文件中每次读或写一个结构。

        

1.输入函数

以下三个函数可用于一次读一个字符。

#include<stdio.h>

int getc(FILE *stream);
//从指定的文件流stream中读取一个字符,并返回其ASCII值
//如果读取到文件末尾或发生错误,则返回EOF

int fgetc(FILE *stream);
//与getc功能相同,从指定文件流stream中读取一个字符,并返回其ASCII值
//如果读取到文件末尾或发生错误,则返回EOF

int getchar(void);
//从标准输入(通常是键盘)读取一个字符,并返回其ASCII值,如果读取到文件末尾或发生错误,则返回EOF。等价于getc(stdin)

注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值。为了区分这两种不同的情况,必须调用ferror或feof。

#include<stdio.h>

int ferror(FILE *stream);
//用于检查指定文件流上的错误标志,判断在进行I/O操作时是否发生了错误。
stream:指向要检查的文件流的指针
返回值:
    如果在指定的文件流上发生了错误,返回非零值。
    如果没有错误,返回0
    
int feof(FILE *stream);
//用于价差指定的文件流是否到达了文件末尾(EOF)
stream:指向要检查的文件流的指针
返回值:
	如果到达文件末尾,返回非零值
    如果没有到达文件末尾,返回0
        
        
void clearerr(FILE *stream);
//用于清除指定文件流上的错误标志和文件结束标志(EOF)。这对于在文件读取或写入过程中发生错误或到达文件末尾后希望继续操作文件非常有用
stream:指向要清除错误和EOF标志的文件流的指针

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

#include<stdio.h>

int ungetc(int ch,FILE *stream);
//用于将字符放回到文件流中,使得他们可以在后续的读取操作中再次读取。这在某些需要”回退“字符的情况下非常有用,例如解析器在遇到某个字符时需要重新考虑其处理逻辑。
ch:要放回到流中的字符。他的类型是int,但实际上它表示一个字符。
stream:指向要放回字符的文件流的指针。
返回值:
	如果成功放回字符,返回ch
    如果发生错误,返回EOF并设置适当的错误标志。
        
压送回流中的字符已有又可从流中读出,但读出字符的顺序与压送回的顺序相反。回送的字符不必一定是上一次读到的字符。不能回送EOF。但是当已经到达文件尾端时,仍可以会送一个字符。下次读将返回该字符,再次读则返回EOF。之所以能这样做的原因是一次成功的ungetc调用会清除该流的文件结束标志。
2.输出函数

        

#include<stdio.h>

int putc(int ch,FILE *stream);
int fputc(int ch,FILE *stream);
int putchar(int ch);
与输入函数一样putchar(c)等效于putc(c,stdout)

对应于上面所述的每个输入函数都有一个输出函数

每次一行I/O

        下面两个函数提供每次输入一行的功能

#include<stdio.h>

char *gets(char *str);
//从标准输入读取一行字符,并存储到str指向的数组中,直到遇到换行符或文件结束符。由于gets不检查输入缓冲区的长度,可能导致缓冲区溢出问题.
gets函数由于不安全,已经在C11标准中被移除
    
char *fgets(char *str,FILE *stream);
//从指定的文件流stream读取最多n-1个字符,并将他们存储到str指向的数组中,数组最后一个字符设置为'\0'。它在读取时会检查缓冲区长度,避免缓冲区溢出问题。

gets与fgets的另一个区别是,gets并不将换行符存入缓冲区。

         fputs和puts提供每次输出一行的功能

#include<stdio.h>

int puts(const char *str);
//用于将字符串写入标准输出(通常是屏幕)。他会自动在字符串末尾添加一个换行符。
如果写入成功,返回非负值
发生错误,返回EOF

int fputs(const char *str,FILE *stream);
//用于将字符串写入指定的流。与puts不同的是,fputs不会在字符串末尾添加换行符。
如果写入成功,返回非负值
发生错误,返回EOF
标准I/O的效率

                                        程序清单5-1 用getc和putc将标准输入复制到标准输出

#include "apue.h"

int main(void)
{
    int c;
    
    while((c=getc(stdin))!=EOF)
        if(putc(c,stdout)==EOF)
            err_sys("output error");
    
    if(ferror(stdin))
        err_sys("input error");
    
    exit(0);
}

                                                         程序清单 5-2 用fgets和fputs将标准输入复制到标准输出

#include "apue.h"

int main()
{
    char buf[MAXLINE];
    
    while(fgets(buf,MAXLINE,stdin)!=NULL)
        if(fputs(buf,stdout)==EOF)
            err_sys("output error");
    
    if(ferror(stdin))
        err_sys("input error");
    
    exit(0);
}

         将三个程序时间与表3-2中的时间进行比较是很有趣的。表5-4中显示了对同一文件(98.5MB,300万行)进行操作所得的数据。

 

 

        用户CPU时间:用户CPU时间是CPU在用户模式下执行程序代码所花费的时间。用户模式是CPU的一种工作模式,在这种模式下,程序的指令只能访问受限的内存和资源。这是为了保护操作系统内核和其他程序的数据和状态。用户CPU时间包括程序执行的所有用户代码,但不包括操作系统内核代码的执行时间。

               示例:当一个程序进行浮点运算,数据处理或其他计算密集型任务时,这些操作通常在用户模式下进行。

        系统CPU时间:系统CPU时间是CPU在内核模式下执行内核代码所花费的时间。内核模式是CPU的另一种工作模式,在这种模式下,程序可以访问所有的内存地址和执行所有的CPU指令,这通常用于操作系统内核和硬件控制。系统CPU时间包括操作系统未执行系统调用,处理硬件中断和管理资源而执行的时间。

        示例:当一个程序进行文件I/O操作,内存分配或其他需要操作系统介入的任务时,这些操作会导致系统CPU时间爱你的增加。

        时钟时间:时钟时间(也称为“实时时间”或“墙上时间”)是从程序开始运行到程序结束所经过的总时间,包括所有CPU时间和其他等待时间。时钟时间包括CPU时间,系统CPU时间以及程序等待的时间(例如等待I/O操作完成,等待其他程序执行完毕,等待系统资源等)。时钟时间是用户感知到的程序运行的总时间。

        示例:一个程序从开始运行到结束花费了10秒钟,其中2秒钟是用于CPU时间,1秒钟是系统CPU时间,剩余的7秒钟是程序等待I/O操作完成的时间,那么时钟时间就是10秒钟。

        对这三个标准I/O版本的每一个,其用户CPU时间都大于表4-2中的最佳read版本,因为在每次读一个字符的标准I/O版本中有一个要执行1亿次循环,而在每次读一行的版本中有一个要执行3144984次的循环。在read版本中,其循环只需执行12611次(对应缓冲区长度为8192字节)。因为系统CPU时间几乎相同,所以用户CPU时间的差别以及等待I/O结束所消耗时间的差别造成了时钟时间的差别。

        系统CPU时间几乎相同,原因是因为所有这些程序对内核提出的读,写请求数基本相同。注意,使用标准I/O例程的一个优点时无需考虑缓冲及最佳I/O长度的选择。在使用fgets是需要考虑最大行长,但是与选择最佳I/O长度比较,这要方便的多。

        这些时间数字的最后一个有趣的方面在于:fgetc版本较表3-2中BUFFSIZE=1的版本要快的多。两者都是用了约2亿次的函数调用,而在用户CPU时间方面,fgetc版本的深度大约是后者的12被,而在时钟时间方面则稍大于25倍。造成这种差别的原因是:使用read版本执行了2亿次的函数调用,这也就造成了2亿次的系统调用。而对于fgetc版本,他也执行了2亿次的函数调用,但是这只引起25222次系统调用。系统调用与普通的函数调用相比通常要花费更多的时间。(只有在缓冲区为空时才触发一次系统调用来填充缓冲区)。

二进制I/O

        如果进行二进制I/O操作,那么我们更愿意一次读或写整个结构。如果使用getc或putc读,写一个结构,那么必须循环通过整个结构,每次循环处理一个字节,一次读或写一个字节,这会非常麻烦而且费时。如果使用fputs和fgets,那么因为fputs在遇到null字节时就停止,而在结构中可能含有null字节,所以不能使用它实现读结构的要求。类似的,如果输入数据中包含有null字节或换行符,则fgets也不能正确工作。因此,提供了下列两个函数执行二进制I/O操作。

#include<stdio.h>

fread和fwrite用于二进制文件输入/输出的函数。他们的作用是从文件中读取和向文件中写入二进制数据。这两个函数可以一次性读写多个字节的数据,效率比逐字节操作高。

size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream);
ptr:指向要读写的数据缓冲区指针
size:每个元素的大小(以字节为单位)
nmemb:元素的数量
stream:文件流指针,指向已打开的文件
    
size_t fwrite(void *ptr,size_t size,size_t nmemb,FILE *stream);

        使用二进制I/O的基本问题是,他只能用于读在同一系统上已写的数据。多年之前,这并无问题(那时,所有UNIX系统都运行于PDP-11上),而现在,很多异构系统通过网络互相连接起来。而且,这种情况已经非常普遍。常常有这种情形,在一个系统上写的数据,要在另一个系统上进行处理。在这种环境下,这两个函数可能就不能正常工作,其原因是:

        (1)在一个结构中,同一成员的偏移量可能因编译器和系统而异(由于不同的对准要求)。确实,某些编译器有一个选项,选择它的不同值,或者使结构中的各成员紧密包装(这可能节省存储空间,而运行性能则可能有所下降);或者准确对其(以便在运行时易于访问结构中的各成员)。这意味着即使在同一个系统上,一个结构的二进制存放方式也可能因编译器选项的不同而不同。

        (2)用来存储多字节整数和浮点值的二进制格式在不同的机器体系结构上也可能不同。

定位流

有三种方法定位标准I/O流

        (1)ftell和fseek函数。这两个函数自V7以来就存在了,但是他们都假定文件的位置可以存放在一个长整型中。

        (2)ftello和fseeko函数。可以是文件偏移量不必一定使用长整型。他们使用off_t数据类型代替了长整型。

        (3)fgetpos和fsetpos函数。这两个函数时由ISO C引入的。他们使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以定义为记录一个文件位置所需的长度。

#include<stdio.h>

long ftell(FILE *stream);
//返回文件指针当前的位置
stream:指向已经打开的文件的文件指针
返回值:
	成功返回文件指针的当前位置(从文件开始的偏移量),单位是字节
    出错时返回-1L,并设置errno
        
int fseek(FILE*stream,long offset,int whence);
//设置文件指针的位置
stream:指向已经打开的文件的文件指针
offset:偏移量,单位是字节
whence:起始位置,可以是以下值之一:
	SEEK_SET:文件开头
    SEEK_CUR:文件指针的当前位置
    SEEK_END:文件末尾
返回值:
	成功时返回0
    出错时返回非零值
        
       
void rewind(FILE *stream);
//将文件指针移动到文件的开头
stream:指向已经打开的文件的文件指针
#include<stdio.h>

ftello和fseeko是标准C库中的函数,与ftell和fseek类似,但它们使用off_t类型的偏移量,以支持大文件(大于2GB)

off_t ftello(FILE *stream);
//返回文件指针当前的位置
返回值:
    成功时返回文件指针的当前位置(从文件开始的偏移量),单位是字节
    出错时返回-1,并设置errno

int fseeko(FILE *stream,off_t offet,int whence);
返回值:
    成功时返回0
    出错时返回非零值
#include<stdio.h>
fgetpos和fsetpos是C标准库中的文件处理函数,用于获取和设置文件流中的当前位置。他们提供了较高精度的文件定位操作。

int fgetpos(FILE *stream,fpos_t *pos);
stream:指向文件流的指针,表示要操作的文件。
pos:指向一个fpos_t类型的变量,用于存储当前文件位置
返回值:
	成功时返回0
    失败时返回非零值,并设置全局变量errno来指示错误
格式化I/O

         格式化输出

         执行格式化输出处理的是4个printf函数

int printf(const char *format,...);
//将格式化的数据输出到标准输出(通常是屏幕)
int fprintf(FILE *stream,const char *format,...);
//将格式化的数据输出到指定的文件流
int sprintf(char *str,const char *format,...);
//将格式化的数据输出到字符串中
int snprintf(char *str,size_t size,const char *format,...);
//将格式化的数据输出到字符串中,但会限制输出的最大长度,以防止缓冲区溢出。

          格式化输入

        执行格式化输入处理的是三个scanf函数

#include<stdio.h>

int scanf(const char *format,...);
//从标准输入(通常是键盘)读取格式化数据
int fscanf(FILE *stream,const char *format,...);
//从指定的文件流读取格式化数据
int sscanf(const char *str,cosnt char *format,...);
//从字符串中读取格式化数据
实现细节

         在UNIX系统中,标准I/O库最终都要调用第三章中说明的I/O例程。每个标准I/O流都有一个与其相关联的文件描述符,可以对一个流调用fileno函数以获得其描述符

#include<stdio.h>

int fileno(FILE *stream);
//用于获取一个文件流FILE对应的文件描述符。文件描述符是一个整数,唯一标识一个打开的文件。
返回值:
    成功时返回文件描述符
    失败时返回-1,并设置errno以指示错误

程序清单5-3中的程序为三个标准流以及一个普通文件相关联的流打印有关缓冲的状态信息。

                                程序清单5-3 对各个标准I/O流打印缓冲状态信息

#include "apue.h"

void pr_stdio(const char *,FILE *); 

int main(void)
{
    FILE *fp;
    
    fputs("enter any character\n",stdout);
    if(getchar()==EOF)
        err_sys("getchar error");
    
    fputs("one line to standard error\n",stderr);
    
    pr_stdio("stdin",stdin);
    pr_stdio("stdout",stdout);
    pr_stdio("stderr",stderr);
    
    if((fp=fopen("/etc/motd","r"))==NULL)
        err_sys("fopen error");
    if(getc(fp)==EOF)
        err_sys("get error");
    
    pr_stdio("/etc/crontab",fp);
    
    exit(0);
}

#define _IO_UNBUFFERED 0x0002
#define _IO_LINE_BUF   0x0040

void pr_stdio(const char *name,FILE *fp)
{
    printf("stream = %s, ",name);
    
    if(fp->_flags&_IO_UNBUFFERED)//无缓冲
        printf("unbuffered");
    else if(fp->_flags&_IO_LINE_BUF)//行缓冲
        printf("line buffered");
    else//全缓冲
        printf("fully buffered");
    
    printf(", buffer size = %d\n",fp->_IO_buf_end-fp->_IO_buf_base);
}

          从中可见,该系统的默认情况是:当标准输入,输出连至终端时,他们是行缓冲的。行缓冲的长度是1024字节。注意,这里并没有将输入,输出的行长限制为1024字节,这只是缓冲区的长度。如果要将2048字节的行写到标准输出,则要进行两次write系统调用。当将这两个流重定向到普通文件时,他们就变成是全缓冲的,其缓冲区长度是该文件系统优先选用的I/O长度(从stat结构中得到的st_blksize值)。从中也可看到,标准出错如它所应该的那样是非缓冲的,而普通文件按系统默认是全缓冲的

临时文件

        ISO C标准I/O库提供了两个函数以帮助创建临时文件。

#include "apue.h"

int main(void)
{
    char name[L_tmpnam],line[MAXLINE];
    FILE *fp;
    
    printf("%s\n",tmpnam(NULL));
    
    tmpnam(name);
    printf("%s\n",name);

	if((fp=tmpfile())==NULL)
		err_sys("tmpfile error");
    
    fputs("one line of output\n",fp);
    rewind(fp);//将文件指针移到开头
    if(fgets(line,sizeof(line),fp)==NULL)
        err_sys("fgets error");
    
    fputs(line,stdout);
    
    exit(0);
}

 

        tmpfile函数经常使用的标准UNIX技术是先调用tmpnam产生一个唯一的李景明,然后用该路径名创建一个文件,并立即unlink它。

#include<stdio.h>

char *tempnam(const char *dir,const char *pfx);
//tempnam是tmpnam的一个变体,它允许调用者为所产生的路径名指定目录和前缀。对于目录有4种不同的选择,按下列顺序判断其条件是否为真,并且使用第一个为真的座位目录:
	(1)如果定义了环境变量TMPDIR,则用其作为目录
     (2)如果参数dir非NULL,则用其作为目录
     (3)将<stdio.h>中的字符串P_tmpdir用作目录
     (4)将本地目录(通常是/tmp)用作目录
如果pfx非NULL,则它应该是做多包含5个字符的字符串,用其作为文件名的头几个字符。该函数调用malloc函数分配动态存储区,用其存放构造的路径名。当不在使用此路径名时就可释放此存储区。

                                            程序清单5-5 演示tempnam函数

#include "apue.h"

int main(int argc,char *argv[])
{
    if(argc!=3)
        err_quit("usage: a.out <directory> <prefix>");
    
    printf("%s\n",tempnam(
        argv[1][0]!=' '?argv[1]:NULL,
        argv[2][0]!=' '?argv[2]:NULL));
    
    exit(0);
}
#include<stdlib.h>

int mkstemp(char *template);
//mkstemp函数是一个更安全的创建临时文件的方法,与tempnam不同,mkstemp函数不仅生成唯一的临时文件名,还会创建文件并返回一个文件描述符,从而避免了在临时文件创建和使用之间可能存在的竞争条件
template:这是一个字符数组,包含文件名模版。模版必须以六个连续的X结尾,例如"tempXXXXXX"。mkstemp会将这六个X替换为随机字符以生成唯一的文件名,并创建文件。
    
返回值:
    成功时,返回一个打开的文件描述符
    失败时,返回-1,并且设置errno以指示错误原因
    
mkstemp创建的临时文件不会被自动删除。如若想从文件系统名字空间中删除该文件,则我们需要自行unlink它。
    
使用tmpnam和tempnam的一个不足之处是:在返回唯一路径名和应用程序用该路径名创建文件之间有一个时间窗口。在该时间窗口期间,另一个进程可能创建一个同名文件。tempfile和mkstemp函数则不会产生此种问题。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值