浅解C语言的标准输入输出、标准错误

来源:互联网 发布:win10未识别的网络wifi 编辑:程序博客网 时间:2024/06/02 03:09

我们都知道用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*对应的缓冲区。






原创粉丝点击