struct _IO_FILE { // Glibc 內部
int _flags; // 標志位: _IO_READ, _IO_WRITE, _IO_EOF, _IO_ERR, _IO_BUF_*...
char *_IO_read_ptr; // 讀緩衝區當前指針
char *_IO_read_end; // 讀緩衝區結束
char *_IO_read_base; // 讀緩衝區基址
char *_IO_write_base; // 寫緩衝區基址
char *_IO_write_ptr; // 寫緩衝區當前指針
char *_IO_write_end; // 寫緩衝區結束
char *_IO_buf_base; // 緩衝區基址 (統一)
char *_IO_buf_end; // 緩衝區結束
int _fileno; // 文件描述符
// ... 鎖、vtable、備用緩衝區等
};
typedef struct _IO_FILE FILE;
FILE *freopen(const char *path, const char *mode, FILE *stream) {
// 1. 關閉原流 (同 fclose 邏輯)
fclose(stream); // 內部調用 _fclose_lk
// 2. 重置流結構體標志/指針
stream->_flags = 0;
stream->_IO_read_ptr = stream->_IO_read_end = stream->_IO_read_base = NULL;
stream->_IO_write_ptr = stream->_IO_write_end = stream->_IO_write_base = NULL;
// 3. 打開新文件 (同 fopen 邏輯)
return fopen(path, mode); // 實際調用 _openfile -> fdopen
}
用途:重定向 stdin/stdout/stderr (如 freopen("log.txt", "w", stdout))。
int fclose(FILE *stream) {
// 1. 刷新寫緩衝區
if (stream->_flags & _IO_WRITE) fflush(stream); // 調用 _IO_do_flush
// 2. 釋放緩衝區內存 (若非用戶提供)
if (stream->_flags & _IO_USER_BUF) ; // 用戶緩衝區不釋放
else free(stream->_IO_buf_base);
// 3. 關閉底層文件描述符
close(stream->_fileno); // 系統調用
// 4. 標志位清理
stream->_flags = 0;
return 0;
}
關鍵:fclose(NULL) 行為未定義,標準要求非 NULL。Glibc 內部有鎖保護 (_IO_lock)。
int feof(FILE *stream) {
return (stream->_flags & _IO_EOF) != 0;
}
int ferror(FILE *stream) {
return (stream->_flags & _IO_ERR) != 0;
}
這兩個標志由讀寫函數 (fread, getc, fputs 等) 在遇到 EOF/錯誤時設置。**注意**:feof() 在讀操作返回 EOF 後** 才為真,**不能**用於循環條件預判:
// 錯誤模式
while (!feof(fp)) { fread(...); } // 會多讀一次
// 正確模式
while (fread(buf, 1, n, fp) == n) { ... } // 或檢查返回值
int fflush(FILE *stream) {
if (!stream) { // NULL: 刷新所有輸出流
for each open stream s:
if (s->_flags & _IO_WRITE) _IO_do_flush(s);
return 0;
}
if (stream->_flags & _IO_WRITE) {
_IO_do_flush(stream); // write() 系統調用
// 可選: fsync() 確保落盤 (由 _IO_SYNC 標志控制)
}
return 0;
}
雙層緩衝:用戶態緩衝區 (FILE*) → 內核頁緩存 → 磁盤控制器緩存 → 物理介質。fflush 推送到內核;fsync/fdatasync 推送到磁盤。
int fgetpos(FILE *stream, fpos_t *pos) {
*pos = _IO_getpos(stream); // 內部包含 _fileno + offset + 狀態
return 0;
}
int fsetpos(FILE *stream, const fpos_t *pos) {
return _IO_setpos(stream, pos); // 內部 lseek + 重置緩衝區
}
fpos_t 是不透明類型,支援 64 位偏移 (LFS),優於 ftell/fseek (長整型可能 32 位)。
| 模式 | 設置 | 觸發刷新條件 | 典型用途 |
|---|---|---|---|
| 全緩衝 | setvbuf(fp, buf, _IOFBF, size) | 緩衝區滿 / fflush / fclose | 文件讀寫 (默認) |
| 行緩衝 | setvbuf(fp, buf, _IOLBF, size) | 遇到換行符 / 緩衝區滿 / fflush | 終端輸出 (stdout 默認) |
| 無緩衝 | setvbuf(fp, NULL, _IONBF, 0) | 每次 I/O 操作即時系統調用 | stderr、調試、進程間通信 |
NULL。FILE* 不能交替讀寫而不調用 fflush/fseek/fsetpos 切換方向 (C 標準要求)。stderr (無緩衝) 或 setvbuf(stdout, NULL, _IONBF, 0)。"rb"/"wb" 關鍵,Linux 無區別但建議顯式寫入。-D_FILE_OFFSET_BITS=64,用 fseeko/ftello 或 fgetpos/fsetpos。FILE* 內部有鎖,單操作原子;跨操作需外部鎖或 flockfile/funlockfile。stdio.h 以 FILE* 封裝用戶態緩衝區、文件描述符、狀態標志、鎖,提供格式化/非格式化、緩衝/非緩衝、文本/二進制多維度 I/O 抽象。理解其內部結構、緩衝區刷新機制、標志位語義、大文件接口,能編寫高性能、健壯、可移植的 C I/O 代碼,並能在調試時直擊底層問題本質。