如何快速、安全跨平台检查FILE*是否指向有效文件
FILE* in Performance-Sensitive Code: Better Alternatives to ftell() Great question—validating a FILE* from fopen() in performance-critical code is a common pain point, especially when you’re looking for something faster and more reliable than ftell(). First, let’s clarify: when fopen() returns a non-NULL pointer, the C standard guarantees it’s a valid stream object. But if you’re worried about the underlying file still being accessible/valid (e.g., deleted by another process, permissions changed) or confirming it’s a regular file (not a pipe/device), ftell() is far from ideal. Here’s why, plus faster, safer cross-platform options:
Why ftell() Isn’t the Right Tool
You’re spot-on to doubt it—here’s the breakdown of its flaws:
- Performance overhead: For text streams,
ftell()may need to traverse the entire stream to calculate the position, which is slow for large files. Even for binary streams, it can involve system calls that add unnecessary latency. - Ambiguous results: If the stream is non-seekable (like a pipe or socket),
ftell()returns -1 and setserrno—but you can’t distinguish between an invalid stream and a perfectly valid non-seekable one. - False sense of security: A successful
ftell()only tells you the stream is seekable, not that the underlying file is still intact. For example, if the file was truncated afterfopen(),ftell()might return a value larger than the current file size, and subsequent reads/writes will fail anyway.
Faster, Safer Alternatives
1. Check Underlying File Descriptor (Most Reliable for Regular Files)
This approach bypasses the C standard library layer to query the OS directly for file state. It’s fast, accurate, and lets you confirm the file is a regular file (not a device/pipe). You’ll need a cross-platform wrapper, but it’s worth it for performance-sensitive code:
POSIX Systems (Linux/macOS)
Use fileno() to get the file descriptor, then fstat() to check its state:
#include <sys/stat.h> #include <stdio.h> bool is_valid_regular_file(FILE* fp) { int fd = fileno(fp); if (fd == -1) return false; struct stat statbuf; if (fstat(fd, &statbuf) == -1) return false; return S_ISREG(statbuf.st_mode); // Confirms it's a regular file }
- Pros: Fast (single system call), directly checks kernel-level file state, confirms file type.
- Cons: Requires POSIX headers; needs a separate implementation for Windows.
Windows
Use _get_osfhandle() to get the Windows file handle, then GetFileInformationByHandle():
#include <windows.h> #include <stdio.h> #include <io.h> bool is_valid_regular_file(FILE* fp) { HANDLE handle = (HANDLE)_get_osfhandle(fileno(fp)); if (handle == INVALID_HANDLE_VALUE) return false; BY_HANDLE_FILE_INFORMATION fileInfo; if (!GetFileInformationByHandle(handle, &fileInfo)) return false; // Check if it's a regular file (not a directory/device) return !(fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); }
2. Lightweight Read Test (Pure Standard C, Cross-Platform)
If you want to avoid OS-specific code, a minimal read-then-unget operation works to verify the stream is still readable. It has almost no overhead and preserves the stream’s state:
#include <stdio.h> bool is_stream_readable(FILE* fp) { int c = fgetc(fp); if (c == EOF) { // Check if this is an error, not just end-of-file if (ferror(fp)) { clearerr(fp); // Reset error flag to avoid breaking future operations return false; } // Empty file is still a valid stream return true; } // Put the character back to restore the stream position ungetc(c, fp); return true; }
- Pros: 100% standard C, no OS dependencies, extremely fast (single read/putback).
- Cons: Only verifies readability (not writability, unless you use
fputc()+fseek()), and can’t distinguish between a regular file and other seekable streams (like pipes, though those are less common in this use case).
Key Notes for Performance-Sensitive Code
- Avoid pre-validation if possible: In multi-threaded/multi-process environments, any validation has a "race window"—the file could become invalid right after you check it. Instead, check
ferror()/feof()immediately after critical read/write operations. - Cache validation results: If you’re performing multiple operations on the same
FILE*, validate once and reuse the result (assuming the file state won’t change during your operations).
内容的提问来源于stack exchange,提问作者JuanPabloJuliosEstebaFiores

