最轻量、最常用的方法是定期调用 stat()(Linux/macOS)或 GetFileAttributesEx()(Windows)比对 st_mtime 字段;须作用于路径而非 fd,注意 Linux 秒级精度及 Windows 时间转换。

用 stat() 检查文件修改时间是否变化
最轻量、最常用的方法是定期调用 stat()(Linux/macOS)或 GetFileAttributesEx()(Windows),比对 st_mtime 字段。它不依赖内容计算,开销极小,适合高频轮询场景。
常见错误现象:fstat() 作用于已打开的 fd,但 fd 对应的 inode 若被替换(如编辑器“写入新文件+原子重命名”),st_mtime 不会变,而文件实际已更新——这时你读到的仍是旧内容,却误判为“未修改”。
- 务必对文件路径调用
stat(),而非对已打开的 fd 调用fstat() - 注意精度:Linux 上
st_mtime默认秒级,高频修改可能漏检;可启用 ext4 的lazytime关闭或改用statx()获取纳秒级时间(需内核 4.11+) - Windows 下用
GetFileTime()取lpLastWriteTime,注意转为本地时间比较前要调用FileTimeToLocalFileTime()
用 inotify(Linux)或 ReadDirectoryChangesW(Windows)监听变更事件
主动监听比轮询更准、更省资源,尤其适合长时间运行的服务程序。但它不是“检测是否被修改”,而是“收到修改通知后做响应”——关键区别在于:通知到达和你实际读取之间仍有窗口,文件可能已被再次修改或删掉。
使用场景:日志文件尾部监控、配置热加载、watchdog 类工具。
立即学习“C++免费学习笔记(深入)”;
- Linux 下
inotify_add_watch(fd, path, IN_MODIFY | IN_MOVED_TO)必须监听IN_MODIFY(内容改)和IN_MOVED_TO(重命名覆盖),缺一不可 - Windows 下
ReadDirectoryChangesW()的dwNotifyFilter至少设FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION - 监听目录比监听单个文件更可靠:很多编辑器先写临时文件再 rename,只监文件会错过
校验和比对必须基于完整快照,不能边读边算
如果真要靠哈希判断内容是否变化,必须在读取前先生成一次完整校验和(如 SHA256),读完后再算一次。边读边更新哈希值(如用 std::hash 或流式 EVP_DigestUpdate)毫无意义——你根本不知道“当前读到哪”和“校验和对应哪段”,无法比对。
性能影响明显:每次校验都要全量读一遍文件,100MB 文件意味着额外 100MB I/O。别在循环里反复调用。
- 优先用系统工具预生成:Linux 可提前跑
sha256sum file > file.sha256,比对时只读小文件 - C++ 中用 OpenSSL 的
EVP_sha256()或跨平台的libsodium(crypto_hash_sha256())比手搓更快更安全 - 校验和本身也要防篡改:把哈希值存在独立路径或内存中,别和原文件放一起且同名(比如
file.sha256很容易被一起覆盖)
“读取过程中被修改”本身是个模糊前提,得先定义清楚边界
POSIX 没有“原子读取整个文件”的保证。所谓“读取过程中”,可能是:fread() 多次调用之间、mmap 映射区域被截断、甚至 read() 返回部分字节后文件被 truncate。这些都不是校验和能解决的,而是 I/O 模型问题。
容易踩的坑:以为加个校验和就万无一失,结果遇到 read() 返回 EBADF 或 EINVAL 才发现 fd 已失效;或者 mmap 后文件缩小,访问末尾页触发 SEGV_MAPERR。
- 对可靠性要求高的场景(如数据库日志),应使用
open()时加O_RDONLY | O_NOFOLLOW,并避免 long-lived fd - 读大文件建议分块 + 每块单独校验(如每 64KB 算一个 CRC32),出错时可定位损坏范围
- 不要依赖
std::ifstream::is_open()判断文件是否还有效——它只说明 fd 没 close,不说明文件没被删或重命名
真正难处理的永远是“修改发生在两次检查之间”这个时间窗口,以及“修改方式绕过你的检测逻辑”——比如符号链接指向切换、overlayfs 层叠覆盖、或 NFS 客户端缓存未同步。这些没法靠一个哈希函数兜底。



















