vs2005的FILE指针

本文通过源代码形式解析用户库如何实现缓冲。通过测试程序及跟踪fread函数,介绍FILE结构体及其成员变量的作用,深入探讨缓冲区的使用策略,包括缓存命中与未命中情况下的处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于用户库如何实现缓冲,本文以源代码的形式简要解释一下。以下是一个测试程序,可以运行的,在fread处下断点,然后一步一步跟踪,你就会了解 iobuf的全貌。顺便说一嘴,vs2005的调试功能很强大,如果仅仅想明白库的设计,那么用vs2005吧,一切尽在眼前
#include "stdafx.h"
#include <stdio.h><br>#include <memory.h><br>int _tmain(int argc, _TCHAR* argv[]) <br>{ <br> char buf[10]; <br> memset(buf,0,10); <br> FILE *f = fopen("e://rc.txt","r"); <br> fread(buf,10,1,f); <br> f-&gt;_bufsiz = 0; //注意不要这样 <br> fread(buf,10,1,f); <br> fflush(f); <br> return 0; <br>} <br>首先看看FILE到底是个什么东西: <br>struct _iobuf { <br> char *_ptr; //下一个字符的指针(下一个指当前缓冲区处理完后的下一个) <br> int _cnt; //当前缓冲区的字符数量 <br> char *_base; //当前缓冲区的地址 <br> int _flag; //标志 <br> int _file; //文件描述符 <br> int _charbuf; <br> int _bufsiz; //当前缓冲区大小 <br> char *_tmpfname; <br> }; <br>typedef struct _iobuf FILE <br>弄 了半天,FILE就是一个如此清晰的结构的指针,结构的字段都是关于缓存的。比如缓存的位置,大小,数量等等,这个_iobuf是不导出的,因为每个平台 的_iobuf可能定义的都不一样,因此仅仅导出FILE,并且不要操作FILE的内部数据,比如不要进行上述的f-&gt;_bufsiz = 0;操作,内部数据的操作由库进行,这个限制一方面简化了用户的操作,另一方面是因为库并不保证每个平台的iobuf实现的字段意义都相同。本文讨论的 vs2005安装的windows平台下的标准库。 <br>fread函数直接调用fread_s <br>//Microsoft Visual Studio 8/VC/crt/src/fread.c <br>size_t __cdecl fread( <br> void *buffer, <br> size_t elementSize, <br> size_t count, <br> FILE *stream <br>) <br>{ <br> /* assumes there is enough space in the destination buffer */ <br> return fread_s(buffer, SIZE_MAX, elementSize, count, stream);//看到这里,再看看linux内核里的vmalloc实现,对比一下参数限制。 <br>} <br>size_t __cdecl fread_s( <br> void *buffer, <br> size_t bufferSize, <br> size_t elementSize, <br> size_t count, <br> FILE *stream <br>) <br>{ <br> size_t retval = 0; <br> if (elementSize == 0 || count == 0) //这个相当于什么也不干,直接返回 <br> { <br> return 0; <br> } <br> /* validation */ <br> _VALIDATE_RETURN((buffer != NULL), EINVAL, 0); <br> if (stream == NULL || count &gt; (SIZE_MAX / elementSize)) <br> { <br> if (bufferSize != SIZE_MAX) <br> { <br> memset(buffer, _BUFFER_FILL_PATTERN, bufferSize); <br> } <br> _VALIDATE_RETURN((stream != NULL), EINVAL, 0); <br> _VALIDATE_RETURN(count } <br> _lock_str(stream); //锁住这个iobuf <br> __try <br> { <br> /* do the read; _fread_nolock_s will make sure we do not buffer overrun */ <br> retval = _fread_nolock_s(buffer, bufferSize, elementSize, count, stream); //开始读,以下读的策略就是如果缓冲区有数据则直接用缓冲区的数据,如果没有再进行文件io。具体实施的时候策略和linux内核的页高速缓存相似。 <br> } <br> __finally <br> { <br> _unlock_str(stream); <br> } <br> return retval; <br>} <br>#define anybuf(s) ((s)-&gt;_flag &amp; (_IOMYBUF|_IONBF|_IOYOURBUF)) <br>size_t __cdecl _fread_nolock_s( <br> void *buffer, <br> size_t bufferSize, <br> size_t elementSize, <br> size_t num, <br> FILE *stream <br>) <br>{ <br> char *data; /* point inside the destination buffer to where we need to copy the read chars */ <br> size_t dataSize; /* space left in the destionation buffer (in bytes) */ <br> size_t total; /* total bytes to read */ <br> size_t count; /* num bytes left to read */ <br> unsigned streambufsize; /* size of stream buffer */ <br> unsigned nbytes; /* how much to read now */ <br> unsigned nread; /* how much we did read */ <br> int c; /* a temp char */ <br> /* initialize local vars */ <br> data = buffer; <br> dataSize = bufferSize; <br> if (elementSize == 0 || num == 0) <br> { <br> return 0; <br> } <br> count = total = elementSize * num; <br> if (anybuf(stream)) //缓存了吗? <br> { <br> /* already has buffer, use its size */ <br> streambufsize = stream-&gt;_bufsiz; <br> } <br> else <br> { <br> /* assume will get _INTERNAL_BUFSIZ buffer */ <br> streambufsize = _INTERNAL_BUFSIZ; <br> } <br> /* here is the main loop -- we go through here until we're done */ <br> while (count != 0) { //循环读取,直到读完 <br> /* if the buffer exists and has characters, copy them to user buffer */ <br> if (anybuf(stream) &amp;&amp; stream-&gt;_cnt != 0) //以上注释很好 <br> { <br> if(stream-&gt;_cnt { <br> _ASSERTE(("Inconsistent Stream Count. Flush between consecutive read and write", stream-&gt;_cnt &gt;= 0)); <br> stream-&gt;_flag |= _IOERR; <br> return (total - count) / elementSize; <br> } <br> /* how much do we want? */ <br> nbytes = (count _cnt) ? (unsigned)count : stream-&gt;_cnt; <br> if (nbytes &gt; dataSize) <br> { <br> if (bufferSize != SIZE_MAX) <br> { <br> memset(buffer, _BUFFER_FILL_PATTERN, bufferSize); <br> } <br> _VALIDATE_RETURN(("buffer too small", 0), ERANGE, 0) <br> } <br> memcpy_s(data, dataSize, stream-&gt;_ptr, nbytes); <br> /* update stream and amt of data read */ <br> count -= nbytes; <br> stream-&gt;_cnt -= nbytes; <br> stream-&gt;_ptr += nbytes; <br> data += nbytes; <br> dataSize -= nbytes; <br> } <br> else if (count &gt;= streambufsize) //超过了io缓冲区 <br> { <br> /* If we have more than streambufsize chars to read, get data <br> by calling read with an integral number of bufsiz <br> blocks. Note that if the stream is text mode, read <br> will return less chars than we ordered. */ <br> /* calc chars to read -- (count/streambufsize) * streambufsize */ <br> nbytes = ( streambufsize ? (unsigned)(count - count % streambufsize) : <br> (unsigned)count ); <br> if (nbytes &gt; dataSize) <br> { <br> if (bufferSize != SIZE_MAX) <br> { <br> memset(buffer, _BUFFER_FILL_PATTERN, bufferSize); <br> } <br> _VALIDATE_RETURN(("buffer too small", 0), ERANGE, 0) <br> } <br> nread = _read(_fileno(stream), data, nbytes); <br> if (nread == 0) { <br> /* end of file -- out of here */ <br> stream-&gt;_flag |= _IOEOF; <br> return (total - count) / elementSize; <br> } <br> else if (nread == (unsigned)-1) { <br> /* error -- out of here */ <br> stream-&gt;_flag |= _IOERR; <br> return (total - count) / elementSize; <br> } <br> /* update count and data to reflect read */ <br> count -= nread; <br> data += nread; <br> dataSize -= nread; <br> } <br> else //没有缓冲命中,直接文件io <br> { <br> /* less than streambufsize chars to read, so call _filbuf to <br> fill buffer */ <br> if ((c = _filbuf(stream)) == EOF) { //这里从文件读取了数据并且填充了缓冲区 <br> /* error or eof, stream flags set by _filbuf */ <br> return (total - count) / elementSize; <br> } <br> /* _filbuf returned a char -- store it */ <br> if (dataSize == 0) <br> { <br> if (bufferSize != SIZE_MAX) <br> { <br> memset(buffer, _BUFFER_FILL_PATTERN, bufferSize); <br> } <br> _VALIDATE_RETURN(("buffer too small", 0), ERANGE, 0) <br> } <br> *data++ = (char) c; <br> --count; <br> --dataSize; <br> /* update buffer size */ <br> streambufsize = stream-&gt;_bufsiz; <br> } <br> } <br> /* we finished successfully, so just return num */ <br> return num; <br>} <br>_filwbuf 的具体实现 <br>int __cdecl _filwbuf ( <br> FILE *str <br> ) <br>#endif /* _UNICODE */ <br>{ <br> REG1 FILE *stream=NULL; <br> /* In safecrt, we assume we always have a buffer */ <br> _VALIDATE_RETURN(str != NULL, EINVAL, _TEOF); <br> /* Init pointer to _iob2 entry. */ <br> stream = str; <br> if (!inuse(stream) || stream-&gt;_flag &amp; _IOSTRG) <br> return(_TEOF); <br> if (stream-&gt;_flag &amp; _IOWRT) { <br> stream-&gt;_flag |= _IOERR; <br> return(_TEOF); <br> } <br> stream-&gt;_flag |= _IOREAD; <br> /* Get a buffer, if necessary. */ <br> if (!anybuf(stream)) <br> { <br>#ifndef _SAFECRT_IMPL <br> _getbuf(stream); //分配缓冲区 <br>#else /* _SAFECRT_IMPL */ <br> /* In safecrt, we assume we always have a buffer */ <br> _VALIDATE_RETURN(FALSE, EINVAL, _TEOF); <br>#endif /* _SAFECRT_IMPL */ <br> } <br> else <br> { <br> stream-&gt;_ptr = stream-&gt;_base; <br> } <br> stream-&gt;_cnt = _read(_fileno(stream), stream-&gt;_base, stream-&gt;_bufsiz); //真正读文件,内部最终调用ReadFile <br>#ifndef _UNICODE <br> if ((stream-&gt;_cnt == 0) || (stream-&gt;_cnt == -1)) { <br>#else /* _UNICODE */ <br> if ((stream-&gt;_cnt == 0) || (stream-&gt;_cnt == 1) || stream-&gt;_cnt == -1) { <br>#endif /* _UNICODE */ <br> stream-&gt;_flag |= stream-&gt;_cnt ? _IOERR : _IOEOF; <br> stream-&gt;_cnt = 0; <br> return(_TEOF); <br> } <br> if ( !(stream-&gt;_flag &amp; (_IOWRT|_IORW)) &amp;&amp; <br> ((_osfile_safe(_fileno(stream)) &amp; (FTEXT|FEOFLAG)) == <br> (FTEXT|FEOFLAG)) ) <br> stream-&gt;_flag |= _IOCTRLZ; <br> /* Check for small _bufsiz (_SMALL_BUFSIZ). If it is small and <br> if it is our buffer, then this must be the first _filbuf after <br> an fseek on a read-access-only stream. Restore _bufsiz to its <br> larger value (_INTERNAL_BUFSIZ) so that the next _filbuf call, <br> if one is made, will fill the whole buffer. */ <br> if ( (stream-&gt;_bufsiz == _SMALL_BUFSIZ) &amp;&amp; (stream-&gt;_flag &amp; <br> _IOMYBUF) &amp;&amp; !(stream-&gt;_flag &amp; _IOSETVBUF) ) <br> { <br> stream-&gt;_bufsiz = _INTERNAL_BUFSIZ; <br> } <br>#ifndef _UNICODE <br> stream-&gt;_cnt--; <br> return(0xff &amp; *stream-&gt;_ptr++); <br>#else /* _UNICODE */ <br> stream-&gt;_cnt -= sizeof(wchar_t); <br> return (0xffff &amp; *((wchar_t *)(stream-&gt;_ptr))++); <br>#endif /* _UNICODE */ <br>} <br>void __cdecl _getbuf ( <br> FILE *str <br> ) <br>{ <br> REG1 FILE *stream; <br> _ASSERTE(str != NULL); <br>#if !defined (CRTDLL) <br> /* force library pre-termination procedure */ <br> _cflush++; <br>#endif /* !defined (CRTDLL) */ <br> /* Init pointers */ <br> stream = str; <br> /* Try to get a big buffer */ <br> if (stream-&gt;_base = _malloc_crt(_INTERNAL_BUFSIZ)) //实际分配缓冲区 <br> { <br> /* Got a big buffer */ <br> stream-&gt;_flag |= _IOMYBUF; <br> stream-&gt;_bufsiz = _INTERNAL_BUFSIZ; <br> } <br> else { <br> /* Did NOT get a buffer - use single char buffering. */ <br> stream-&gt;_flag |= _IONBF; <br> stream-&gt;_base = (char *)&amp;(stream-&gt;_charbuf); <br> stream-&gt;_bufsiz = 2; <br> } <br> stream-&gt;_ptr = stream-&gt;_base; <br> stream-&gt;_cnt = 0; <br> return; <br>} <br>int __cdecl _fileno ( <br> FILE *stream <br> ) <br>{ <br> _VALIDATE_RETURN((stream != NULL), EINVAL, -1); <br> return( stream-&gt;_file ); <br>} <br>最终读取文件 <br>int __cdecl _read ( <br> int fh, <br> void *buf, <br> unsigned cnt <br> ) <br>{ <br> int r; /* return value */ <br> /* validate handle */ <br> _CHECK_FH_CLEAR_OSSERR_RETURN( fh, EBADF, -1 ); <br> _VALIDATE_CLEAR_OSSERR_RETURN((fh &gt;= 0 &amp;&amp; (unsigned)fh _VALIDATE_CLEAR_OSSERR_RETURN((_osfile(fh) &amp; FOPEN), EBADF, -1); <br> _lock_fh(fh); /* lock file */ <br> __try { <br> if ( _osfile(fh) &amp; FOPEN ) <br> r = _read_nolock(fh, buf, cnt); /* read bytes */ <br> else { <br> errno = EBADF; <br> _doserrno = 0; <br> r = -1; <br> _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0)); <br> } <br> } <br> __finally { <br> _unlock_fh(fh); /* unlock file */ <br> } <br> return r; <br>} <br>以 上就是用户空间缓冲读写中关于读的全部,关于缓冲读写还有一个很重要的函数就是fflush,这个函数刷新缓冲区,将缓冲的数据写入磁盘,注意,在 fopen时如果flag带有c字符,那么无论如何缓冲区的数据在调用fflush时是要写入磁盘的,这里的缓冲区包括用户缓冲区(就是iobuf结构中的缓冲区)和内核缓冲区。关于fflush可以在vs2005中同样的方式跟踪,该函数实际是很清晰的。 <br>可以看出不管用户库的实现还是内核的实现,对于缓冲的策略都是相同的,而且用户库的实现一点也不比内核的实现简单。</memory.h></stdio.h>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值