我们都知道用printf( xxx )和fprintf( stdin, xxx )是一个效果。所以stdin是一个FILE*类型的变量。同样的stdout和stderr也是。
他们的定义在stdio.h(GNU实现)里,其中部分代码如下:
/* Standard streams. */ extern struct _IO_FILE *stdin; /* Standard input stream. */ extern struct _IO_FILE *stdout; /* Standard output stream. */ extern struct _IO_FILE *stderr; /* Standard error output stream. */ #ifdef __STDC__ /* C89/C99 say they're macros. Make them happy. */ #define stdin stdin #define stdout stdout #define stderr stderr #endif
规范要求这几个变量必需是宏,所以后面跟了几个宏,当然这么做是有风险的,例如如下代码:
int main() { setbuf( stdout, NULL ); fprintf( stdout, "hello, " ); stdout = stdout + 1; fprintf( stdout, "world! "); return 0; }编译不会有问题,但是运行时段错误。后来我又看了一下微软的标准库代码,是这样的:
#define stdin (&__iob_func()[0]) #define stdout (&__iob_func()[1]) #define stderr (&__iob_func()[2])从一个函数返回值中取地址,这时stdout就不是左值了,是禁止被赋值的,编译时就会出错。
在VC6时代,微软似乎也是用了一个静态数组_iob[],直接从这个数组中取值,这和GNU实现方法差不多,都有被改变值的危险。参看:http://blog.csdn.net/wanglei5695312/article/details/5402607
其实FILE是一个结构体_IO_FILE的别名宏,里面包含了文件描述符号,读写指针偏移值等信息,在libio.h中可以找到:
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
我们知道,linux操作系统都是通过文件描述符号(一个整数值)来确定对文件的api调用的,而windows是通过维护一个[文件描述符,句柄]的map,直接通过文件描述符获取内核句柄的,具体参看:http://www.cnblogs.com/fullsail/archive/2012/10/21/2732873.html
这个值就是上面结构体中的_fileno,那么stdin、stdout、stderr对应的文件描述符号分别是多少呢?我们来看unistd.h中的一段代码:
/* Standard file descriptors. */ #define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO 1 /* Standard output. */ #define STDERR_FILENO 2 /* Standard error output. */
一个intel面试题是写出下列代码输出:
int main() { fprintf( stdout, "hello, " ); fprintf( stderr, "world! "); return 0; }