其他的库文件看起来没有什么实现层面的知识可以探究的,所以,直接来看stdio.h,stdlib.h,string.h。
1.茶余饭后的杂谈,有趣的历史
在过去的几十年中,独立于设备的输入输出模型得到了飞速的发展,标准C从这个改善的模型中获益颇丰。
输入输出模块
在20世纪60年代早期,FORTRAN IV被认为是独立于机器的语言。但是如果不作任何改动,根本不可能在各种计算机体系结构中移动FORTRAN IV程序。可移植性的主要障碍是输入输出领域。在FORTRAN IV中,可以对FORTRAN IV代码中间的I/O语句中对正在通信的设备进行命名。CARD 和 INPUT TAPE就不一样。
之后,逐渐发展到使用逻辑单元号(LUN)来代替具体的设备名,从而在可执行的二进制卡片之前加入控制卡片,从而指定某个特殊的运行过程那些设备与特定的LUN相对应。这时候,独立于设备的I/O时代来临了。
设备独立的进一步改善得益于标准外围交换程序(peripheral interchange program,PIP)的进步。该程序允许指定源设备与目标设备的任意成,然后尽力执行两个设备之间的拷贝操作。
进入UNIX。UNIX对所有文本流采用了标准内部形式,文本的每一行以换行符终止。这正是程序读入文本时所期望的,也是程序输出所产生的。假如这样的约定不能满足和UNIX机器相连的处理文本的外围设备的需求,可以在系统的对外接口有些修改,不必修改任何内部代码。UNIX提供了两种机制来修正“对外接口”的文本流。首先的是一个通用的映射函数,它可以用任意的文本处理设备工作。可以用系统调用ioctl来设置或者测试一个具体设备的的各种参数。另一个修正文本流的机制是修改直接控制该设备的专门软件。对于每一个UNIX可能需要控制的设备来说,用户必须添加一个常驻UNIX的设备管理器。
当第一个C编译器在UNIX平台上运行时,C语言就自然地继承了它的宿主操作系统简单的I/O模型。除了文本流的统一表示,还有其他一些优点。很久以前使用的LUNs在最近几十年也慢慢地演变为称为文件描述符或句柄的非常小的正整数。操作系统负责分发文件描述符。并且把所有的文件控制信息存储在自己的专用内存中,而不是让用户去分配和维持文件和记录控制块以加重负担。
为了简化多数程序的运行管理,UNIX shell分配给每个运行的程序3个标准文件描述符,这些就是现在普通使用的标准输入、标准输出和标准错误流。(文本流)
UNIX不会阻止向任意打开的文件写入任意的二进制编码,或者从一个足够大的地方把它们丝毫不变地读取出来。(二进制流)
所以,UNIX消除了文本流(与人通信)和二进制流(与机器通信)之间的区别。
在相同的实现下,从一个二进制流读入的数据应该和之前写入到这个liu的数据相同,而文本流则不是。
关于文本流和二进制流的参考资料:http://www.embedu.org/Column/Column186.htm,有助于理解概念。
PS:流是一个操作系统层面的高度抽象的概念,它隐藏了I/O设备具体的实质,而将所有的I/O带来的数据变化看做输入的流入和流出,这样,在操作系统层面为程序将各种I/O设备模拟成流的样式,已经使这时的I/O模块独立而抽象了。可以看到,I/O模型发展的过程,就是其逐渐抽象统一的过程,这一点与语言的发展的历程是相似的。
X3J11委员会在1983年开始召开会议为C起草ANSI标准。非UNIX系统的C厂商和那些UNIX用户之间争论了很长时间,因为UNIX用户不能理解I/O为什么要这么麻烦(显然,UNIX的文件结构和设备的管理机制保证了I/O模块的简洁性,这是相对于其他操作系统的优点)。这是一个很有教育意义的过程,这些争论的一个重要的副产品就是更清楚地阐明了C支持的I/O模块。
最终,委员会经过讨论整洁的重要性和向下兼容的重要性之后,决定抛弃UNIX风格的原语。(主要平衡代码效率和代码简洁性)
2.不识庐山真面露,包含的内容
类型:
FILE 它是一个对象类型,可以记录控制流需要的所有信息,包括它的文件定位符、指向相关缓冲(如果有的话)的指针、记录是否发生了读/写错误的错误提示符和记录文件手否结束的文件结束符(用来控制流的FILE对象的地址可能很重要,不必使用FILE对象的副本来代替原始的对象进行服务。)
库中的函数分两类:
1.针对任意流的操作;
2.指定特定问文件流的操作;
两者分别又有读写、文件定位、缓冲区控制等操作,可以完成对流的全方位操作,只要你能想到。
3.不畏浮云遮望眼,看实现吧
有两种设计决策对<stdio.h>的实现非常关键:
类型FILE:
------------------------------------------------------------------------------------------------------------------------------------------------------------
(此处纯为个人理解,不为原书内容)
不管是二进制流还是文本流,C都是将文件当做连续的字节流在处理。该字节流的信息以及文件对应的文件描述符等都是需要存储在FILE类型中的内容。
typedef struct _iobuf { char *_ptr; int _cnt; char *_base; int _flag; int _file; int _charbuf; int _bufsiz; char *_tmpfname; }FILE;
虽然不知道这些个变量是什么意思,但看一下一些函数的实现可以勉强猜一猜。
1.FOPEN
1 FILE * __cdecl _tfsopen ( 2 const _TSCHAR *file, 3 const _TSCHAR *mode 4 ,int shflag 5 ) 6 { 7 REG1 FILE *stream; 8 REG2 FILE *retval; 9 10 _ASSERTE(file != NULL); 11 _ASSERTE(*file != _T(‘\0‘)); 12 _ASSERTE(mode != NULL); 13 _ASSERTE(*mode != _T(‘\0‘)); 14 15 /* Get a free stream */ 16 /* [NOTE: _getstream() returns a locked stream.] */ 17 18 if ((stream = _getstream()) == NULL) 19 return(NULL); 20 21 /* open the stream */ 22 #ifdef _UNICODE 23 retval = _wopenfile(file,mode,shflag,stream); 24 #else /* _UNICODE */ 25 retval = _openfile(file,mode,shflag,stream); 26 #endif /* _UNICODE */ 27 28 /* unlock stream and return. */ 29 _unlock_str(stream); 30 return(retval); 31 }
其中FILE *类型变量通过_getstream()获得。所以,我们可以稍微看下_getstream()的实现。不过在看_getstream()的实现之前,有必要介绍C标准库中关于IO控制块(即FILE文件)的管理机制:
首先,在I/O控制块中有三个特殊的控制块,分别是标准输入、标准输出和标准错误流。其中标准输入有其输入大小的限制。
#define _INTERNAL_BUFSIZ 4096 char _bufin[_INTERNAL_BUFSIZ]; //标准输入流使用的存储单元
可以看到我使用的这个版本的C标准库中,标准输入流的大小为:4096。我们可以简单用代码测试一下:
1 int main(void) 2 { 3 4 int i; 5 char s[10000]; 6 for( i = 0; i < 10000; i++ ) 7 s[i] = 0; 8 scanf("%s",s); 9 for(i = 0; i < 10000; i++ ){ 10 if( s[i] == 0){ 11 printf("%d",i+1); 12 break; 13 } 14 } 15 return 0; 16 }
输入为5000个1,输出结果为:4095。应该是有一个结束标志符的缘故。显然,那些超出_INTERNAL_BUFSIZ的字符串部分被舍去了。
该C标准库中,这个I/O控制块通过一个FILE数组_iob和一个FILE **指针来进行管理。如下:
代码块1:
#ifdef _WIN32 #define _NSTREAM_ 512 /* * Number of entries in _iob[] (declared below). Note that _NSTREAM_ must be * greater than or equal to _IOB_ENTRIES. */ #define _IOB_ENTRIES 20 #else /* _WIN32 */ #ifdef CRTDLL #define _NSTREAM_ 128 /* *MUST* match the value under ifdef _DLL! */ #else /* CRTDLL */ #ifdef _DLL #define _NSTREAM_ 128 #else /* _DLL */ #ifdef _MT #define _NSTREAM_ 40 #else /* _MT */ #define _NSTREAM_ 20
代码块2:
FILE _iob[_IOB_ENTRIES] = { /* _ptr, _cnt, _base, _flag, _file, _charbuf, _bufsiz */ /* stdin (_iob[0]) */ { _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ }, /* stdout (_iob[1]) */ { NULL, 0, NULL, _IOWRT, 1, 0, 0 }, /* stderr (_iob[3]) */ { NULL, 0, NULL, _IOWRT, 2, 0, 0 }, };
代码块3:
#ifdef CRTDLL int _nstream = _NSTREAM_; #else /* CRTDLL */ int _nstream; #endif /* CRTDLL */
void ** __piob;
if ( (__piob = (void **)_calloc_crt( _nstream, sizeof(void *) )) == NULL ) { _nstream = _IOB_ENTRIES; if ( (__piob = (void **)_calloc_crt( _nstream, sizeof(void *))) == NULL ) _amsg_exit( _RT_STDIOINIT ); } for ( i = 0 ; i < _IOB_ENTRIES ; i++ ) __piob[i] = (void *)&_iob[i];
从代码块2中我们可以看到,FILE _iob[_IOB_ENTRIES]是一个FILE数组,其中预设了三种FILE类型,分别是stdin,stdout和stderr。因为不同平台下,I/O控制块数量的大小至少为20(从_NSTREAM_的定义看出),所以_IOB_ENTRIES定义为20,作为前20个I/O控制块。此处的值是不是20其实没有什么意义,只要这个数组能容纳下3个预设的I/O控制块,同时大小至于产生浪费空间的可能即可(大于20就有可能浪费)。
__piob是一个FILE **的二维指针,管理着一个FILE *的指针数组,用来指向陆续分配的I/O控制块的地址。
管理方式如图:
现在,我们将目光移回,看下_getstream()的实现:
1 FILE * __cdecl _getstream ( 2 void 3 ) 4 { 5 REG2 FILE *retval = NULL; 6 7 #ifdef _WIN32 8 9 REG1 int i; 10 11 /* Get the iob[] scan lock */ 12 _mlock(_IOB_SCAN_LOCK); 13 14 /* 15 * Loop through the __piob table looking for a free stream, or the 16 * first NULL entry. 17 */ 18 for ( i = 0 ; i < _nstream ; i++ ) { 19 20 if ( __piob[i] != NULL ) { 21 /* 22 * if the stream is not inuse, return it. 23 */ 24 if ( !inuse( (FILE *)__piob[i] ) ) { 25 #ifdef _MT 26 _lock_str2(i, __piob[i]); 27 28 if ( inuse( (FILE *)__piob[i] ) ) { 29 _unlock_str2(i, __piob[i]); 30 continue; 31 } 32 #endif /* _MT */ 33 retval = (FILE *)__piob[i]; 34 break; 35 } 36 } 37 else { 38 /* 39 * allocate a new _FILEX, set _piob[i] to it and return a 40 * pointer to it. 41 */ 42 if ( (__piob[i] = _malloc_crt( sizeof(_FILEX) )) != NULL ) { 43 44 #if defined (_MT) 45 InitializeCriticalSection( &(((_FILEX *)__piob[i])->lock) ); 46 EnterCriticalSection( &(((_FILEX *)__piob[i])->lock) ); 47 #endif /* defined (_MT) */ 48 retval = (FILE *)__piob[i]; 49 } 50 51 break; 52 } 53 } 54 55 /* 56 * Initialize the return stream. 57 */ 58 if ( retval != NULL ) { 59 retval->_flag = retval->_cnt = 0; 60 retval->_tmpfname = retval->_ptr = retval->_base = NULL; 61 retval->_file = -1; 62 } 63 64 _munlock(_IOB_SCAN_LOCK); 65 66 #else /* _WIN32 */ 67 #if defined (_M_MPPC) || defined (_M_M68K) 68 69 REG1 FILE *stream = _iob; 70 71 /* Loop through the _iob table looking for a free stream.*/ 72 for (; stream <= _lastiob; stream++) { 73 74 if ( !inuse(stream) ) { 75 stream->_flag = stream->_cnt = 0; 76 stream->_tmpfname = stream->_ptr = stream->_base = NULL; 77 stream->_file = -1; 78 retval = stream; 79 break; 80 } 81 } 82 83 #endif /* defined (_M_MPPC) || defined (_M_M68K) */ 84 #endif /* _WIN32 */ 85 86 return(retval); 87 }
显然,在仍有FILE*指针可用的情况下,为第一个空闲的FILE *分配一片对应的FILE空间。即将新的stream纳入到了整个I/O控制块的管理中。
OK,我们再回到fopen函数中,在得到一个没有使用过的I/O控制块之后,显然下一步要做的就是对这个I/O块,根据设定的模式进行配置。此时,要调用到的就是_openfile函数。在_openfile中,需要标记了stream.flag = streamflag;streamflag通过位标记了_IOREAD、_IOWRT当前所进行操作的类型。stream.file得到了一个int类型的文件标识符(通过更底层的open系列函数建立了文件标识符,同时决定了该数据流是文本流还是二进制流)。
那么,我们可以得到FILE中两个变量的意义了。
The Standard C library (stdio.h)阅读笔记1,布布扣,bubuko.com
The Standard C library (stdio.h)阅读笔记1
原文:http://www.cnblogs.com/shijiezhenmei/p/3655456.html