我們都知道用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*對應的緩衝區。