| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 2518 人关注过本帖
标题:【求助】C标准库一书中的“原语”一词求解释【共同学习】
只看楼主 加入收藏
meiyounichen
Rank: 2
等 级:论坛游民
威 望:1
帖 子:35
专家分:29
注 册:2012-12-31
收藏
得分:0 
回复 10楼 zklhp
ok,那我把发言编辑了...给各位添麻烦了

[ 本帖最后由 meiyounichen 于 2013-1-26 15:25 编辑 ]
2013-01-26 15:23
c10121209
Rank: 1
等 级:新手上路
帖 子:18
专家分:5
注 册:2013-1-25
收藏
得分:0 
回复 9楼 meiyounichen
好啊,我已经加你了。
2013-01-26 15:58
c10121209
Rank: 1
等 级:新手上路
帖 子:18
专家分:5
注 册:2013-1-25
收藏
得分:0 
程序代码:
<fopen.c>  定义了 <stdio.h> 函数 fopen(...) {打开文件}

/***
*FILE *fopen(file, mode) - 打来文件
*
*目的:
*       打来文件指定一个流.  mode 决定了文件的打开方式和属性:
*       C标准提供的模式:
*        r 可读 w 清空后可写可建 a 不清空可写可建,写位置位于文件末尾。
*        r+ 不清空可读可写,写位置在文件开头。
*        w+ 清空后可读可写可建。
*        a+ 不清空可读可写可建,写位置位于文件末尾。
*        b 二进制文件模式。如果操作系统只有一种文件模式,加不加b都行。
*        第一个字母必须是r w a 之一,第二个字母是 + 或 b ,第三个字母是 + 或 b 或 没有。
*
*Entry:
*       char *file - 文件名或路径
*       char *mode - 文件访问模式
*
*Exit:
*       成功返回指向流的指针
*       失败返回NULL
*
*Exceptions:
*
*******************************************************************************/

FILE * __cdecl _tfopen (
        const _TSCHAR *file,
        const _TSCHAR *mode
        )
{
        return( _tfsopen(file, mode, _SH_DENYNO) );          //window实现的标准库中有一个fopen的安全版本,根据_tfsopen(...)的第三个参数决定使用的是哪个版本。
        //引用一下C语言的精神中的第一条“相信程序员”,觉得window的做法没多大意思。
}

//fopen(...) 的具体实现
FILE * __cdecl _tfsopen (
                         const _TSCHAR *file,
                         const _TSCHAR *mode
                         ,int shflag
                         )
{
    FILE *stream = NULL;                                      //接收流地址的变量
    FILE *retval = NULL;                                      //返回值

    _VALIDATE_RETURN((file != NULL), EINVAL, NULL);
    _VALIDATE_RETURN((mode != NULL), EINVAL, NULL);
    _VALIDATE_RETURN((*mode != _T('\0')), EINVAL, NULL);      //上面三个是对参数的合法性检验

    /* 获得一个空闲流 */
    /* [注意: _getstream() 返回一个锁住的流.] */

    if ((stream = _getstream()) == NULL)                      //获得流 从 _iob[] 中 或 新创建一个 _FILEX 资源
    {
        errno = EMFILE;                               //配置错误信息
        return(NULL);                                 //返回失败值
    }

    __try {  //异常处理块
        /* We deliberately don't hard-validate for emptry strings here. All other invalid
        path strings are treated as runtime errors by the inner code in _open and openfile.
        This is also the appropriate treatment here. Since fopen is the primary access point
        for file strings it might be subjected to direct user input and thus must be robust to
        that rather than aborting. The CRT and OS do not provide any other path validator (because
        WIN32 doesn't allow such things to exist in full generality).
        */
        if(*file==_T('\0'))                                   //文件名参数不允许为空
        {
            errno = EMFILE;                                   //配置错误信息
            return(NULL);                                     //返回失败值
        }

        /* 打开流,即将流与文件之间建立联系 */
#ifdef _UNICODE
        retval = _wopenfile(file,mode,shflag,stream);         //unicde字符集版本
#else  /* _UNICODE */
        retval = _openfile(file,mode,shflag,stream);          //ASCII字符集版本
#endif  /* _UNICODE */

        //_openfile(...)负责建立联系,打开文件,创建文件等的具体操作。调用成功返回与stream相同的值,失败返回NULL。

    }
    __finally {
        _unlock_str(stream);                                  //将流解锁,不管上一步是否成功,都将流解锁。
    }

    return(retval);                                           //返回
}

上面的_getstream(...) 确实有问题,我自己发现一个。
_iob[] 的元素类型就是 FILE。

欢迎大家指正。

接下来去解读_openfile(...)
闪了。
2013-01-26 16:01
c10121209
Rank: 1
等 级:新手上路
帖 子:18
专家分:5
注 册:2013-1-25
收藏
得分:0 
程序代码:
<_open.c>  

/***
*FILE *_openfile(filename, mode, shflag, stream) - 通过模式字符串和共享标志打开一个文件.
*
*目的:
*       解析字符串, 查看是否恰当的有 {rwa} 中的一个, 至多一个 '+',
*       至多一个 {tb}, 至多一个 {cn}, 至多一个 {SR}, 至多一个 'T', 至多一个 'D'.
*        通过shflag的结果.
*        如果权限允许的话以恰当的方式打开一个文件. 直到第一次io操作不分配缓冲区。
*        只建议在库内使用。
*
*进入:
*       char *filename - 要打开的文件
*       char *mode - 打开模式
*       int shflag - 文件的共享标志
*       FILE *stream - 使用文件的流
*
*退出:
*       配置流的参数, 引用系统文件的管理 通过流的调用。
*       返回 流 或 NULL
*
*Exceptions:
*
*******************************************************************************/

FILE * __cdecl __topenfile (
                            const _TSCHAR *filename,
                            REG3 const _TSCHAR *mode,
                            int shflag,
                            FILE *str
                            )
{
    int modeflag;
    int streamflag = _commode;                    //设置默认的流状态模式为未指派 int _commode = 0;
    int commodeset = 0;
    int scanset    = 0;
    int whileflag;
    int filedes;
    FILE *stream;
    BOOL encodingFlag = FALSE;

    _ASSERTE(filename != NULL);
    _ASSERTE(mode != NULL);
    _ASSERTE(str != NULL);                        //检验参数

    /*解析用户的打开模式配置下面两处的参数。
    (1) modeflag - 系统调用标志词
    (2) streamflag - 流句柄标志词. */

    /* 去掉前端的空格 */
    while (*mode == _T(' '))
    {
        ++mode;                                   //说明 fopen 第二个参数前面可以有空格出现。
    }

    /* 第一个字符必须是 'r', 'w', 或 'a'. */

    switch (*mode) {                                             //modeflag 的参数在 <fcntl.h> 中 , streamflag 的参数在 <stdio.h> 中
        case _T('r'):
            modeflag = _O_RDONLY;                                //文件只读
            streamflag |= _IOREAD;                               //流可读
            break;
        case _T('w'):
            modeflag = _O_WRONLY | _O_CREAT | _O_TRUNC;          //文件只写 | 文件可创建 | 文件清空 (根据后面的字符不同,含义可变)
            streamflag |= _IOWRT;                                //流可写
            break;
        case _T('a'):
            modeflag = _O_WRONLY | _O_CREAT | _O_APPEND;         //文件只写 | 文件可创建 | 文件末尾添加
            streamflag |= _IOWRT;                                //流可写
            break;
        default:
            _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
    }

    /* 下面接可能有三组选项的模式字符:
    (1) A single '+' character,
    (2) One of 't' and 'b' and
    (3) One of 'c' and 'n'.
    */

    //标准C中只规定了 b 和 + 所以只考虑这两种情况。

    whileflag=1;                                                     //这个变量保证每个字符只能出现一次。

    while(*++mode && whileflag)
        switch(*mode) {

                case _T(' '):                                        //跳过空格
                    /* skip spaces */
                    break;

                case _T('+'):
                    if (modeflag & _O_RDWR)                          //如果包含可读可写的标志位,说明 + 已经出现过了,停止解析。
                        whileflag=0;
                    else {
                        modeflag |= _O_RDWR;                         //设置可读可写的标志位,清除读 或 写的标志位。
                        modeflag &= ~(_O_RDONLY | _O_WRONLY);
                        streamflag |= _IORW;
                        streamflag &= ~(_IOREAD | _IOWRT);
                    }
                    break;

                case _T('b'):                                        //设置二进制文件的标志位。
                    if (modeflag & (_O_TEXT | _O_BINARY))
                        whileflag=0;
                    else
                        modeflag |= _O_BINARY;
                    break;

                    //下面的忽略
                case _T('t'):
                    if (modeflag & (_O_TEXT | _O_BINARY))
                        whileflag=0;
                    else
                        modeflag |= _O_TEXT;
                    break;

                case _T('c'):
                    if (commodeset)
                        whileflag=0;
                    else {
                        commodeset = 1;
                        streamflag |= _IOCOMMIT;
                    }
                    break;

                case _T('n'):
                    if (commodeset)
                        whileflag=0;
                    else {
                        commodeset = 1;
                        streamflag &= ~_IOCOMMIT;
                    }
                    break;

                case _T('S'):
                    if (scanset)
                        whileflag=0;
                    else {
                        scanset = 1;
                        modeflag |= _O_SEQUENTIAL;
                    }
                    break;

                case _T('R'):
                    if (scanset)
                        whileflag=0;
                    else {
                        scanset = 1;
                        modeflag |= _O_RANDOM;
                    }
                    break;

                case _T('T'):
                    if (modeflag & _O_SHORT_LIVED)
                        whileflag=0;
                    else
                        modeflag |= _O_SHORT_LIVED;
                    break;

                case _T('D'):
                    if (modeflag & _O_TEMPORARY)
                        whileflag=0;
                    else
                        modeflag |= _O_TEMPORARY;
                    break;
                case _T('N'):
                    modeflag |= _O_NOINHERIT;
                    break;

                case _T(','):
                    encodingFlag = TRUE;
                    whileflag = 0;
                    break;


                default:
                    _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
    }
    if (encodingFlag)
    {//这部分忽略
        static const _TSCHAR ccsField[] = _T("ccs");
        static const _TSCHAR utf8encoding[] = _T("UTF-8");
        static const _TSCHAR utf16encoding[] = _T("UTF-16LE");
        static const _TSCHAR unicodeencoding[] = _T("UNICODE");

        /* Skip spaces */
        while (*mode == _T(' '))
        {
            ++mode;
        }

        /*
        * The length that we want to compare is numbers of elements in
        * csField -1 since this number also contains NULL terminator
        */
        if (_tcsncmp(ccsField, mode, (_countof(ccsField))-1) != 0)
            _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);

        mode += _countof(ccsField)-1;

        /* Skip spaces */
        while (*mode == _T(' '))
        {
            ++mode;
        }

        /* Look for '=' */
        if (*mode != _T('='))
        {
            _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);
        }
        ++mode;

        /* Skip spaces */
        while (*mode == _T(' '))
        {
            ++mode;
        }

        if (_tcsnicmp(mode, utf8encoding, _countof(utf8encoding) - 1) == 0){
            mode += _countof(utf8encoding)-1;
            modeflag |= _O_U8TEXT;
        }
        else if (_tcsnicmp(mode, utf16encoding, _countof(utf16encoding) - 1) == 0) {
            mode += _countof(utf16encoding)-1;
            modeflag |= _O_U16TEXT;
        }
        else if (_tcsnicmp(mode, unicodeencoding, _countof(unicodeencoding) - 1) == 0) {
            mode += _countof(unicodeencoding)-1;
            modeflag |= _O_WTEXT;
        }
        else
            _VALIDATE_RETURN(("Invalid file open mode",0), EINVAL, NULL);

    }

    /* Skip trailing spaces */
    while (*mode == _T(' '))                                    //掉过空格
    {
        ++mode;
    }

    _VALIDATE_RETURN( (*mode == _T('\0')), EINVAL, NULL);       //保证 mode 中除了标准要求的没有其他别的字符了。

    /* 尝试打开文件.  注意如果没有b 或t, 就使用默认的文件模式. */

    if (_tsopen_s(&filedes, filename, modeflag, shflag, _S_IREAD | _S_IWRITE) != 0)    //打开文件的具体操作。成功返回0,失败返回-1.会返回文件描述符filedes
        return(NULL);

    /*配置流的基本信息 */
#ifndef CRTDLL
    _cflush++;  /* 促使 库 每次终止 程序(步骤) */                  //这个变量不清楚是干什么的,猜测和关闭程序时要关闭所有的文件这步操作有关。
#endif  /* CRTDLL */
    /* Init pointers */
    stream = str;                                                    //赋值给返回值

    stream->_flag = streamflag;                                      //设置流的状态标志参数
    stream->_cnt = 0;                                                //没弄清的参数
    stream->_tmpfname = stream->_base = stream->_ptr = NULL;         //一堆指针置为空

    stream->_file = filedes;                                         //文件的文件描述符

    return(stream);                                                  //返回
}
下面接着 _tsopen_s(...)

求指点 求意见 求笼罩。。。。
2013-01-26 17:38
c10121209
Rank: 1
等 级:新手上路
帖 子:18
专家分:5
注 册:2013-1-25
收藏
得分:0 
看了一下_tsopen_s(...)/* _sopen, _open */ 发现里面就是解析了打开文件模式等这些参数来配置调用CreateFile这个API的参数。
里面挺乱的,没怎么看全,就不贴出来了。

现在总结一下:

fopen(...) 先进入一个预先准备的表中去查找一个没有使用的流(FILE),若没有找到就使用一个用于扩充表的数据结构_FILEX来创建一个新的流,然后添加到表中。
在这个查表和对流的初始化的过程中要保证访问资源的所有权是唯一的。然后在解析打来模式等参数来配置CreateFile这个底层API的参数,调用它来打开或新建一个文件。
2013-01-27 12:00
c10121209
Rank: 1
等 级:新手上路
帖 子:18
专家分:5
注 册:2013-1-25
收藏
得分:0 
fflush(。。。)
程序代码:
/***
*fflush.c - 刷新一个流的缓冲区
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*目的:
*       定义 fflush() - 刷新一个流的缓冲区
*               _flushall() - 刷新所有流的缓冲区
*
*******************************************************************************/

#include <sect_attribs.h>
#include <cruntime.h>
#include <stdio.h>
#include <file2.h>
#include <io.h>
#include <mtdll.h>
#include <internal.h>


/* 通过 flsall() 的值来区分 _flushall() 和

 * fflush(NULL) 的调用行为

 */
#define FLUSHALL        1         //flush all
#define FFLUSHNULL      0         //fflush null

/* fflush(NULL) 和 flushall() 的核心程序

 */
static int __cdecl flsall(int);


/***
*int fflush(stream) - 刷新一个流的缓冲区
*
*目的:
*       如果文件以写和缓冲的方式打开, 刷新缓冲区. 如果以有问题的
*       方式刷新缓冲区, 设置流的状态为错误。
*       Always flushes the stdio stream and forces a commit to disk if file
*       was opened in commit mode.
*
*进入:
*       FILE *stream - 将刷洗的流
*
*退出:
*       成功返回0, 失败或没有缓冲区
*       返回EOF and 如果失败设置文件错误标记.
*       影响 FILE 结构体的参数有: _ptr, _cnt, _flag.
*
*Exceptions:
*
*******************************************************************************/


int __cdecl fflush (
        FILE *stream
        )
{
        int rc;                                       //定义返回值

        /* 如果参数为NULL,刷新所有的流
         */
        if ( stream == NULL )
                return(flsall(FFLUSHNULL));

        //刷新单个流的操作
        _lock_str(stream);                     //锁定流

        __try {
                rc = _fflush_nolock(stream);   //具体操作
        }
        __finally {
                _unlock_str(stream);           //解锁流
        }

        return(rc);
}


/***
*_fflush_nolock() - 刷新在一个流上的缓冲区 (此时流已经被锁定)
*
*/
int __cdecl _fflush_nolock (
        FILE *str
        )
{

        /* 如果参数为NULL,刷新所有的流。
         */
        if ( str == NULL )
                return(flsall(FFLUSHNULL));


        if (_flush(str) != 0) {
                /* 调用失败, 不要尝试提交 */
                return(EOF);          //返回EOF 说明操作失败了。
        }

        /* 底层io提交 to 确保数据写入到磁盘 */
        if (str->_flag & _IOCOMMIT) {
                return (_commit(_fileno(str)) ? EOF : 0);
        }
        return 0;
}

int __cdecl _flush (                                //刷新单个流的操作
        FILE *str
        )
{
         FILE *stream;
        int rc = 0; /* 预设正确返回结果 */
         int nchar;

        /* 初始化指向流的指针 */
        stream = str;


        if ((stream->_flag & (_IOREAD | _IOWRT)) == _IOWRT && bigbuf(stream)
                && (nchar = (int)(stream->_ptr - stream->_base)) > 0)
                /*这里做了三个判断:
                1.判断流的状态只有可写一种,猜测:在可读可写的文件中文件的状态会在读和写之间切换。
                2.bigbuf 不明白是什么意思,也许是说明这个文件有缓冲区。
                3._base 指向缓冲区的头, _ptr 指向缓冲区的最后一个字符的下一位置。代表有待写的字符。*/
        {
                if ( _write(_fileno(stream), stream->_base, nchar) == nchar ) {     //输出操作的底层原语
                        /* 如果这是一个可读可写文件, 清除它的写状态
                         * 使下一个操作能是一个读。
                         */
                        if ( _IORW & stream->_flag )
                                stream->_flag &= ~_IOWRT;
                }
                else {
                        stream->_flag |= _IOERR;      //出现错误了。
                        rc = EOF;
                }
        }

        stream->_ptr = stream->_base;     //不管读写操作是否成功都清空缓冲区。
        stream->_cnt = 0;

        return(rc);
}

// <fileno.c>

int __cdecl _fileno (
        FILE *stream
        )
{
        _VALIDATE_RETURN((stream != NULL), EINVAL, -1);          //检验流不是NULL
        return( stream->_file );                                 //返回流的文件句柄
}

// end <fileno.c>

// <commit.c>

//这个函数负责提交操作系统中的缓冲数据

int __cdecl _commit (
                     int filedes
                     )
{
    int retval;          //定义返回值

    /* 如果句柄值超出返回, 投诉 */
    _CHECK_FH_RETURN( filedes, EBADF, -1 );
    _VALIDATE_RETURN((filedes >= 0 && (unsigned)filedes < (unsigned)_nhandle), EBADF, -1);
    _VALIDATE_RETURN((_osfile(filedes) & FOPEN), EBADF, -1);          //一堆检验

    _lock_fh(filedes);                                                           //锁定文件句柄
    __try {
        if (_osfile(filedes) & FOPEN) {      //如果(if) filedes这个文件句柄已经打开(FOPEN)

            if ( !FlushFileBuffers((HANDLE)_get_osfhandle(filedes)) ) {   //_get_osfhandle(int)  获得文件的实际句柄 这里的句柄是封装的
                //windows API: BOOL FlushFileBuffers(HANDLE h); 刷新文件的缓冲区并把缓冲区中的所有数据写入到文件。
                //参数h,是一个已经打开了的文件的句柄,必须有可写权限。
                //成功返回非0,失败返回0,错误信息由 GetLastError() 返回。
                //摘录一段MSDN:
                /*To open a file for unbuffered I/O, call the CreateFile function with the FILE_FLAG_NO_BUFFERING flag. 
                This prevents the file contents from being cached. 
                However, the file metadata may still be cached. To flush the metadata to a disk, use FlushFileBuffers*/
                /*以没有缓冲的方式打开一个文件,在调用CreateFile时使用FILE_FLAG_NO_BUFFER标记
                这会阻止文件内容存在缓存
                无论如何,文件的元数据依然会写入缓存,刷新元数据到磁盘,依然用FlushFileBuffers*/
                retval = GetLastError();
            }
            else {
                retval = 0;     /* 返回成功 */
            }

            /* map the OS return code to C errno value and return code */
            if (retval == 0)
                goto good;       //成功后直接跳转

            _doserrno = retval;

        }

        errno = EBADF;
        retval = -1;               //失败返回-1

        _ASSERTE(("Invalid file descriptor. File possibly closed by a different thread",0));
good :
        ; }               //这里有意思,接了一个空语句
    __finally {
        _unlock_fh(filedes);                                                  //解锁句柄
    }
    return (retval);
}

// end <commit.c>


/***
*int _flushall() - flush all output buffers
*
*Purpose:
*       刷新所有输出流的缓冲区, 清除多有输入流的缓冲区。.
*
*Entry:
*       None.
*
*Exit:
*       返回打开的流的数量。
*
*Exceptions:
*
*******************************************************************************/

int __cdecl _flushall (
        void
        )
{
        return(flsall(FLUSHALL));
}


/***
*static int flsall(flushflag) - flush all output buffers
*
*Purpose:
*       Flushes all the output buffers to the file and, if FLUSHALL is passed,
*       clears all input buffers. Core routine for both fflush(NULL) and
*       flushall().
*
*       所线程注意: 所有线程锁的申请和释放都在这个程序里面执行.
*
*Entry:
*       int flushflag - 这个参数决定被要求的语义, 有两个法定值: FLUSHALL 和 FFLUSHNULL
*
*Exit:
*       if flushflag == FFLUSHNULL then flsbuf returns:
                0, 如果成功
*               EOF, 只要有一个失败。
*
*       if flushflag == FLUSHALL 返回被成功刷新的流的数量
*
*Exceptions:
*
*******************************************************************************/

static int __cdecl flsall (
        int flushflag
        )
{
        int i;
        int count = 0;
        int err = 0;

        _mlock(_IOB_SCAN_LOCK);             //锁定总表
        __try {

        for ( i = 0 ; i < _nstream ; i++ ) {

                if ( (__piob[i] != NULL) && (inuse((FILE *)__piob[i])) ) {      //__piob可能指向FILE或_FILEX

                        /*
                         * 锁定流. 直到确定流在使用中才做这个操作来避免创建多余的线程锁 
                         * 代价就是在流被锁定后不得不再测试一次。
                         */
                        _lock_str2(i, __piob[i]);                            //锁定流

                        __try {
                                if ( inuse((FILE *)__piob[i]) ) {            //再次测试,因为这个流可能在锁定之前的瞬间被关闭。

                        if ( flushflag == FLUSHALL ) {                       //这个模式下任何状态的流都会被调用刷新操作
                                if ( _fflush_nolock(__piob[i]) != EOF )
                                        count++;                             //成功后的计数
                        }
                        else if ( (flushflag == FFLUSHNULL) &&
                                  (((FILE *)__piob[i])->_flag & _IOWRT) ) {  //只对有写状态的流才会调用刷新操作
                                if ( _fflush_nolock(__piob[i]) == EOF )
                                        err = EOF;                           //有一个失败就返回错误。
                        }

                                }
                        }
                        __finally {
                                _unlock_str2(i, __piob[i]);                  //解锁流
                        }
                }
        }

        }
        __finally {
                _munlock(_IOB_SCAN_LOCK);         //解锁总表
        }

        if ( flushflag == FLUSHALL )              //跟据情况返回。
                return(count);
        else
                return(err);
}


大概总结一下:
标准库的IO操作分了两个缓冲区,一个是由操作系统之上的代码实现的一个字符数组,一个是window系统的缓存缓冲区。
刷新一个流的时候,先将字符数组缓冲区中的数据使用win32API函数WriteFile写到系统中,再利用win32API函数FlushFileBuffers刷新系统的缓存缓冲区。
刷新缓冲区要求写状态的流,不管什么流调用刷新操作都会将字符数组缓冲区清空。
如果流的状态为可读可写,那么他的状态会在 读 和 写 之间切换。

[ 本帖最后由 c10121209 于 2013-1-28 14:53 编辑 ]
2013-01-28 14:39
快速回复:【求助】C标准库一书中的“原语”一词求解释【共同学习】
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.023781 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved