应改用 lseek(POSIX)或 _lseeki64(Windows)绕过 stdio 缓冲直接操作文件描述符,每次轮询前 lseek(fd, 0, SEEK_END) 获取真实大小并与上次 offset 比较,大小变化则 lseek(fd, last_offset, SEEK_SET) 读增量;Linux 优先 inotify,Windows 必须 ReadDirectoryChangesW;logrotate 场景需监听 inode 变更并重开文件。

如何用 fseek + ftell 安全地定位文件末尾
直接 fseek(fp, 0, SEEK_END) 看似简单,但对正在被写入的日志文件(尤其是多进程/多线程追加)极易出错:某些系统下 ftell 可能返回不准确的偏移,或 fseek 后 fread 读不到新内容。根本原因是标准库 FILE 流有内部缓冲,且未同步底层文件状态。
实操建议:
- 改用
lseek(POSIX)或_lseeki64(Windows)绕过 stdio 缓冲,直接操作文件描述符 - 每次轮询前先
lseek(fd, 0, SEEK_END)获取真实大小,再与上次记录的offset比较 - 若大小不变,sleep 后重试;若变大,
lseek(fd, last_offset, SEEK_SET)再读增量 - 注意:Windows 上需用
_open配合_read,不能混用fopen和lseek
为什么 inotify / ReadDirectoryChangesW 比轮询更可靠
轮询本质是“猜”文件是否变化,而 inotify(Linux)和 ReadDirectoryChangesW(Windows)是内核通知机制,事件触发即读,无延迟、无遗漏、不耗 CPU。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 轮询间隔设为 100ms,但日志每 50ms 写一行 → 丢行
- 轮询时文件被 logrotate 切走,
lseek失败却没检查返回值 → 无限循环报错
使用场景:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- Linux 下优先用
inotify_add_watch(fd, path, IN_MODIFY)监听追加 - Windows 下必须用
ReadDirectoryChangesW,注意它监听目录而非单个文件,需过滤出目标文件名 - 两者都需非阻塞或搭配
select/epoll使用,避免卡死
std::ifstream 的 peek() 和 gcount() 在实时读取中容易失效
很多人试图用 std::ifstream 配合 seekg + peek 实现“看看有没有新内容”,结果发现 peek() 总返回 EOF,或 gcount() 为 0 —— 这是因为 ifstream 默认启用缓冲,且不会自动响应外部文件增长。
参数差异与规避方式:
-
ifstream构造时不加std::ios::binary,换行符处理可能干扰偏移计算 - 调用
clear()可清除failbit,但无法恢复已错失的数据流位置 - 真正可行的做法:关闭流,用
open()重新打开(Linux 下仍可读原 inode),或干脆弃用ifstream,改用read()系统调用
logrotate 场景下如何不丢日志、不重复读
这是最常被忽略的复杂点:logrotate 会 rename 原文件并创建新空文件,但你的程序还持着旧 fd。此时 lseek(fd, 0, SEEK_END) 返回的是旧文件大小(即使它已被 mv 走),新内容全写进新文件,你完全感知不到。
解决方案只有两个方向:
- 监听文件名变更:inotify 的
IN_MOVED_FROM+IN_CREATE组合可捕获 rename 和新建动作,但需维护文件名到 fd 的映射 - 放弃监控文件名,改监控 inode:用
stat()比较st_ino,一旦不一致,就close旧 fd、open新路径(注意路径可能已变) - Windows 下无 inode 概念,只能靠定时
GetFileInformationByHandle对比dwVolumeSerialNumber+nFileIndexHigh/Low
实际项目里,inode 方案更通用,但必须在每次读前做一次 stat 校验,不能只在启动时查一次。


















