实例.docx

上传人:b****8 文档编号:8964524 上传时间:2023-05-16 格式:DOCX 页数:24 大小:26.07KB
下载 相关 举报
实例.docx_第1页
第1页 / 共24页
实例.docx_第2页
第2页 / 共24页
实例.docx_第3页
第3页 / 共24页
实例.docx_第4页
第4页 / 共24页
实例.docx_第5页
第5页 / 共24页
实例.docx_第6页
第6页 / 共24页
实例.docx_第7页
第7页 / 共24页
实例.docx_第8页
第8页 / 共24页
实例.docx_第9页
第9页 / 共24页
实例.docx_第10页
第10页 / 共24页
实例.docx_第11页
第11页 / 共24页
实例.docx_第12页
第12页 / 共24页
实例.docx_第13页
第13页 / 共24页
实例.docx_第14页
第14页 / 共24页
实例.docx_第15页
第15页 / 共24页
实例.docx_第16页
第16页 / 共24页
实例.docx_第17页
第17页 / 共24页
实例.docx_第18页
第18页 / 共24页
实例.docx_第19页
第19页 / 共24页
实例.docx_第20页
第20页 / 共24页
亲,该文档总共24页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

实例.docx

《实例.docx》由会员分享,可在线阅读,更多相关《实例.docx(24页珍藏版)》请在冰点文库上搜索。

实例.docx

实例

实例——fopen和getc函数的实现

下面以标准库函数fopen和getc的一种实现方法为例来说明如何将这些系统调用结合

起来使用。

我们回忆一下,标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的。

文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:

一个指向缓冲

区的指针,通过它可以一次读入文件的一大块内容;一个记录缓冲区中剩余的字符数的计数

器;一个指向缓冲区中下一个字符的指针;文件描述符;描述读/写模式的标志;描述错误

状态的标志等。

描述文件的数据结构包含在头文件中,任何需要使用标准输入/输出库中函

数的程序都必须在源文件中包含这个头文件(通过#include指令包含头文件)。

此文件也被

库中的其它函数包含。

在下面这段典型的代码段中,只供标准库中其它函数所使

用的名字以下划线开始,因此一般不会与用户程序中的名字冲突。

所有的标准库函数都遵循

该约定。

#defineNULL0

#defineEOF(-1)

#defineBUFSIZ1024

#defineOPEN_MAX20/*max#filesopenatonce*/

typedefstruct_iobuf{

intcnt;/*charactersleft*/

char*ptr;/*nextcharacterposition*/

char*base;/*locationofbuffer*/

intflag;/*modeoffileaccess*/

intfd;/*filedescriptor*/

}FILE;

externFILE_iob[OPEN_MAX];

#definestdin(&_iob[0])

#definestdout(&_iob[1])

#definestderr(&_iob[2])

enum_flags{

_READ=01,/*fileopenforreading*/

_WRITE=02,/*fileopenforwriting*/

_UNBUF=04,/*fileisunbuffered*/

_EOF=010,/*EOFhasoccurredonthisfile*/

_ERR=020/*erroroccurredonthisfile*/

};

int_fillbuf(FILE*);

int_flushbuf(int,FILE*);

#definefeof(p)((p)->flag&_EOF)!

=0)

#defineferror(p)((p)->flag&_ERR)!

=0)

#definefileno(p)((p)->fd)

#definegetc(p)(--(p)->cnt>=0\

?

(unsignedchar)*(p)->ptr++:

_fillbuf(p))

#defineputc(x,p)(--(p)->cnt>=0\

?

*(p)->ptr++=(x):

_flushbuf((x),p))

#definegetchar()getc(stdin)

#defineputcher(x)putc((x),stdout)

宏getc一般先将计数器减1,将指针移到下一个位置,然后返回字符。

(前面讲过,一

个长的#define语句可用反斜杠分成几行。

)但是,如果计数值变为负值,getc就调用函数

_fillbuf填充缓冲区,重新初始化结构的内容,并返回一个字符。

返回的字符为unsigned

类型。

以确保所有的字符为正值。

尽管在这里我们并不想讨论一些细节,但程序中还是给出了putc函数的定义,以表明它

的操作与getc函数非常类似,当缓冲区满时,它将调用函数_flushbuf。

此外,我们还在

其中包含了访问错误输出、文件结束状态和文件描述符的宏。

下面我们来着手编写函数fopen。

fopen函数的主要功能是打开文件,定位到合适的位

置,设置标志位以指示相应的状态。

它不分配任何缓冲区空间,缓冲区的分配是在第一次读

文件时由函数_fillbuf完成的。

#include

#include"syscalls.h"

#definePERMS0666/*RWforowner,group,others*/

FILE*fopen(char*name,char*mode)

{

intfd;

FILE*fp;

if(*mode!

='r'&&*mode!

='w'&&*mode!

='a')

returnNULL;

for(fp=_iob;fp<_iob+OPEN_MAX;fp++)

if((fp->flag&(_READ|_WRITE))==0)

break;/*foundfreeslot*/

if(fp>=_iob+OPEN_MAX)/*nofreeslots*/

returnNULL;

if(*mode=='w')

fd=creat(name,PERMS);

elseif(*mode=='a'){

if((fd=open(name,O_WRONLY,0))==-1)

fd=creat(name,PERMS);

lseek(fd,0L,2);

}else

fd=open(name,O_RDONLY,0);

if(fd==-1)/*couldn'taccessname*/

returnNULL;

fp->fd=fd;

fp->cnt=0;

fp->base=NULL;

fp->flag=(*mode=='r')?

_READ:

_WRITE;

returnfp;

}

该版本的fopen函数没有涉及标准C的所有访问模式,但是,加入这些模式并不需要增加多

少代码。

特别是,该版本的fopen不能识别表示二进制访问方式的b标志,这是因为,在

UNIX系统中这种方式是没有意义的。

同时,它也不能识别允许同时进行读和写的+标志。

对于某一特定的文件,第一次调用getc函数时计数值为0,这样就必须调用一次函数

_fillbuf。

如果_fillbuf发现文件不是以读方式打开的,它将立即返回EOF;否则,它将

试图分配一个缓冲区(如果读操作是以缓冲方式进行的话)。

建立缓冲区后,_fillbuf调用read填充此缓冲区,设置计数值和指针,并返回缓冲区

中的第一个字符。

随后进行的_fillbuf调用会发现缓冲区已经分配。

#include"syscalls.h"

/*_fillbuf:

allocateandfillinputbuffer*/

int_fillbuf(FILE*fp)

{

intbufsize;

if((fp->flag&(_READ|_EOF_ERR))!

=_READ)

returnEOF;

bufsize=(fp->flag&_UNBUF)?

1:

BUFSIZ;

if(fp->base==NULL)/*nobufferyet*/

if((fp->base=(char*)malloc(bufsize))==NULL)

returnEOF;/*can'tgetbuffer*/

fp->ptr=fp->base;

fp->cnt=read(fp->fd,fp->ptr,bufsize);

if(--fp->cnt<0){

if(fp->cnt==-1)

fp->flag|=_EOF;

else

fp->flag|=_ERR;

fp->cnt=0;

returnEOF;

}

return(unsignedchar)*fp->ptr++;

}

最后一件事情便是如何执行这些函数。

我们必须定义和初始化数组_iob中的stdin、

stdout和stderr值:

FILE_iob[OPEN_MAX]={/*stdin,stdout,stderr*/

{0,(char*)0,(char*)0,_READ,0},

{0,(char*)0,(char*)0,_WRITE,1},

{0,(char*)0,(char*)0,_WRITE,|_UNBUF,2}

};

该结构中flag部分的初值表明,将对stdin执行读操作、对stdout执行写操作、对stderr

执行缓冲方式的写操作。

练习8-2用字段代替显式的按位操作,重写fopen和_fillbuf函数。

比较相应代

码的长度和执行速度。

练习8-3设计并编写函数_flushbuf、fflush和fclose。

练习8-4标准库函数

intfseek(FILE*fp,longoffset,intorigin)

类似于函数lseek,所不同的是,该函数中的fp是一个文件指针而不是文件描述符,且返回

值是一个int类型的状态而非位置值。

编写函数fseek,并确保该函数与库中其它函数使用

的缓冲能够协同工作。

8.6.实例——目录列表

我们常常还需要对文件系统执行另一种操作,以获得文件的有关信息,而不是读取文件

的具体内容。

目录列表程序便是其中的一个例子,比如__________UNIX命令ls,它打印一个目录中的文

件名以及其它一些可选信息,如文件长度、访问权限等等。

MS-DOS操作系统中的dir命令也

有类似的功能。

由于UNIX中的目录就是一种文件,因此,ls只需要读此文件就可获得所有的文件名。

是,如果需要获取文件的其它信息,比如长度等,就需要使用系统调用。

在其它一些系统中,

甚至获取文件名也需要使用系统调用,例如在MS-DOS系统中即如此。

无论实现方式是否同

具体的系统有关,我们需要提供一种与系统无关的访问文件信息的途径。

以下将通过程序fsize说明这一点。

fsize程序是ls命令的一个特殊形式,它打印命令

行参数表中指定的所有文件的长度。

如果其中一个文件是目录,则fsize程序将对此目录递

归调用自身。

如果命令行中没有任何参数,则fsize程序处理当前目录。

我们首先回顾UNIX文件系统的结构。

在UNIX系统中,目录就是文件,它包含了一个

文件名列表和一些指示文件位置的信息。

“位置”是一个指向其它表(即i结点表)的索引。

文件的i结点是存放除文件名以外的所有文件信息的地方。

目录项通常仅包含两个条目:

文件

名和i结点编号。

遗憾的是,在不同版本的系统中,目录的格式和确切的内容是不一样的。

因此,为了分

离出不可移植的部分,我们把任务分成两部分。

外层定义了一个称为Dirent的结构和3个

函数opendir、readdir和closedir,它们提供与系统无关的对目录项中的名字和i结点

编号的访问。

我们将利用此接口编写fsize程序,然后说明如何在与Version7和SystemV

UNIX系统的目录结构相同的系统上实现这些函数。

其它情况留作练习。

结构Dirent包含i结点编号和文件名。

文件名的最大长度由NAMZ_MAX设定,NAME_MAX

的值由系统决定。

opendir返回一个指向称为DIR的结构的指针,该结构与结构FILE类似,

它将被readdir和closedir使用。

所有这些信息存放在头文件dirent.h中。

#defineNAME_MAX14/*longestfilenamecomponent;*/

/*system-dependent*/

typedefstruct{/*portabledirectoryentry*/

longino;/*inodenumber*/

charname[NAME_MAX+1];/*name+'\0'terminator*/

}Dirent;

typedefstruct{/*minimalDIR:

nobuffering,etc.*/

intfd;/*filedescriptorforthedirectory*/

Direntd;/*thedirectoryentry*/

}DIR;

DIR*opendir(char*dirname);

Dirent*readdir(DIR*dfd);

voidclosedir(DIR*dfd);

系统调用stat以文件名作为参数,返回文件的i结点中的所有信息;若出错,则返回-1。

如下所示:

char*name;

structstatstbuf;

intstat(char*,structstat*);

stat(name,&stbuf);

它用文件name的i结点信息填充结构stbuf。

头文件中包含了描述stat

的返回值的结构。

该结构的一个典型形式如下所示:

structstat/*inodeinformationreturnedbystat*/

{

dev_tst_dev;/*deviceofinode*/

ino_tst_ino;/*inodenumber*/

shortst_mode;/*modebits*/

shortst_nlink;/*numberoflinkstofile*/

shortst_uid;/*ownersuserid*/

shortst_gid;/*ownersgroupid*/

dev_tst_rdev;/*forspecialfiles*/

off_tst_size;/*filesizeincharacters*/

time_tst_atime;/*timelastaccessed*/

time_tst_mtime;/*timelastmodified*/

time_tst_ctime;/*timeoriginallycreated*/

};

该结构中大部分的值已在注释中进行了解释。

dev_t和ino_t等类型在头文件

中定义,程序中必须包含此文件。

st_mode项包含了描述文件的一系列标志,这些标志在中定义。

我们只

需要处理文件类型的有关部分:

#defineS_IFMT0160000/*typeoffile:

*/

#defineS_IFDIR0040000/*directory*/

#defineS_IFCHR0020000/*characterspecial*/

#defineS_IFBLK0060000/*blockspecial*/

#defineS_IFREG0010000/*regular*/

/*...*/

下面我们来着手编写程序fsize。

如果由stat调用获得的模式说明某文件不是一个目

录,就很容易获得该文件的长度,并直接输出。

但是,如果文件是一个目录,则必须逐个处

理目录中的文件。

由于该目录可能包含子目录,因此该过程是递归的。

主程序main处理命令行参数,并将每个参数传递给函数fsize。

#include

#include

#include"syscalls.h"

#include/*flagsforreadandwrite*/

#include/*typedefs*/

#include/*structurereturnedbystat*/

#include"dirent.h"

voidfsize(char*)

/*printfilename*/

main(intargc,char**argv)

{

if(argc==1)/*default:

currentdirectory*/

fsize(".");

else

while(--argc>0)

fsize(*++argv);

return0;

}

函数fsize打印文件的长度。

但是,如果此文件是一个目录,则fsize首先调用dirwalk

函数处理它所包含的所有文件。

注意如何使用文件中的标志名S_IFMT和

S_IFDIR来判定文件是不是一个目录。

括号是必须的,因为&运算符的优先级低于==运算符

的优先级。

intstat(char*,structstat*);

voiddirwalk(char*,void(*fcn)(char*));

/*fsize:

printthenameoffile"name"*/

voidfsize(char*name)

{

structstatstbuf;

if(stat(name,&stbuf)==-1){

fprintf(stderr,"fsize:

can'taccess%s\n",name);

return;

}

if((stbuf.st_mode&S_IFMT)==S_IFDIR)

dirwalk(name,fsize);

printf("%8ld%s\n",stbuf.st_size,name);

}

函数dirwalk是一个通用的函数,它对目录中的每个文件都调用函数fcn一次。

它首

先打开目录,循环遍历其中的每个文件,并对每个文件调用该函数,然后关闭目录返回。

为fsize函数对每个目录都要调用dirwalk函数,所以这两个函数是相互递归调用的。

#defineMAX_PATH1024

/*dirwalk:

applyfcntoallfilesindir*/

voiddirwalk(char*dir,void(*fcn)(char*))

{

charname[MAX_PATH];

Dirent*dp;

DIR*dfd;

if((dfd=opendir(dir))==NULL){

fprintf(stderr,"dirwalk:

can'topen%s\n",dir);

return;

}

while((dp=readdir(dfd))!

=NULL){

if(strcmp(dp->name,".")==0

||strcmp(dp->name,".."))

continue;/*skipselfandparent*/

if(strlen(dir)+strlen(dp->name)+2>sizeof(name))

fprintf(stderr,"dirwalk:

name%s%stoolong\n",

dir,dp->name);

else{

sprintf(name,"%s/%s",dir,dp->name);

(*fcn)(name);

}

}

closedir(dfd);

}

每次调用readdir都将返回一个指针,它指向下一个文件的信息。

如果目录中已没有待处理

的文件,该函数将返回NULL。

每个目录都包含自身“.”和父目录“..”的项目,在处理时

必须跳过它们,否则将会导致无限循环。

到现在这一步为止,代码与目录的格式无关。

下一步要做的事情就是在某个具体的系统

上提供一个opendir、readdir和closedir的最简单版本。

以下的函数适用于Version7

和SystemVUNIX系统,它们使用了头文件(sys/dir.h>中的目录信息,如下所示:

#ifndefDIRSIZ

#defineDIRSIZ14

#endif

structdirect{/*directoryentry*/

ino_td_ino;/*inodenumber*/

chard_name[DIRSIZ];/*longnamedoesnothave'\0'*/

};

某些版本的系统支持更长的文件名和更复杂的目录结构。

类型ino_t是使用typedef定义的类型,它用于描述i结点表的索引。

在我们通常使用

的系统中,此类型为unsignedshort,但是这种信息不应在程序中使用。

因为不同的系统

中该类型可能不同,所以使用typedef定义要好一些。

所有的“系统”类型可以在文件

opendir函数首先打开目录,验证此文件是一个目录(调用系统调用fstat,它与stat

类似,但它以文件描述符作为参数),然后分配一个目录结构,并保存信息:

intfstat(intfd,structstat*);

/*opendir:

openadirectoryforreaddircalls*/

DIR*opendir(char*dirname)

{

intfd;

structstatstbuf;

DIR*dp;

if((fd=open(dirname,O_RDONLY,0))==-1

||fstat(fd,&stbuf)==-1

||(stbuf.st_mode&S_IFMT)!

=S_IFDIR

||(dp=(DIR*)malloc(sizeof(DIR)))==NULL)

returnNULL;

dp->fd=fd;

returndp;

}

closedir函数用于关闭目录文件并释放内存空间:

/*closedir:

closedirectoryopenedbyopendir*/

voidclosedir(DIR*dp)

{

if(dp){

close(dp->fd);

free(dp);

}

}

最后,函数readdir使用read系统调用读取每个目录项。

如果某个目录位置当前没有

使用(因为删除了一个文件),则它的i结点编号为0,并跳过该位置。

否则,将i结点编号

和目录名放在一个static类型的结构中,并给用户返回一个指向此结构的指针。

每次调用

readdir函数将覆盖前一次调用获得的信息。

#include/*localdirectorystructure*/

/*readdir:

readdirectoryentriesinsequence*/

Dirent*readdir(DIR*dp)

{

structdirectdirbu

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2