我们都知道用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. */
另外,提一下stdout和stderr的输出模式的区别,默认情况下,stdout上绑定了一个缓冲区,会在遇到换行符‘\n’或缓冲区满时输出,而stderr是立即输出。
一个intel面试题是写出下列代码输出:
int main() {
fprintf( stdout, "hello, " );
fprintf( stderr, "world! ");
return 0;
}
输出应该是“world!hello, ”,当然可以通过setbuf/setvbuf函数来改变一个FILE*对应的缓冲区。