vs2005的FILE指针

晨曦之光 发布于 2012/04/10 15:02
阅读 607
收藏 0

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


原文链接:http://blog.csdn.net/dog250/article/details/5303368
加载中
返回顶部
顶部