ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

cppcheck IOWithoutPositioning 检查器:捕获 C/C++ 文件读写间缺失定位操作导致的未定义行为

2026/10/4 15:40:25 拓冰建站 浏览量
cppcheck IOWithoutPositioning 检查器:捕获 C/C++ 文件读写间缺失定位操作导致的未定义行为 开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载IOWithoutPositioning 是 cppcheck 内置的 I/O 类检查器之一专门检测在同时以读写模式打开的FILE*流上读操作与写操作之间缺少fseek()/fsetpos()/rewind()/fflush()等定位或刷新调用的情况——按 C 标准这属于未定义行为Undefined Behaviour。读完本文你将掌握该检查器的触发条件、修复方式、cppcheck 的底层跟踪算法与能力边界以及如何在真实项目中启用和验证它。检查器基本信息属性值消息 IDIOWithoutPositioning消息文本Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour.类别Undefined Behaviour未定义行为严重级别Error适用语言C / C该检查器的核心实现在 lib/checkio.cppvoid CheckIOImpl::ioWithoutPositioningError(const Token *tok) { reportError(tok, Severity::error, IOWithoutPositioning, Read and write operations without a call to a positioning function (fseek, fsetpos or rewind) or fflush in between result in undefined behaviour., CWE664, Certainty::normal); }从源码可见它按Severity::error级别报告关联 CWE-664Improper Control of a Resource Through its Lifetime置信度为Certainty::normal。问题背景C 标准对读写切换的要求C 标准规定在同一个以读写模式打开的文件流上若读取操作之后紧跟写入操作或反之且中间没有调用定位函数fseek、fsetpos、rewind或fflush()则该行为是未定义的。尽管许多实现如 glibc 的某些版本在特定缓冲条件下恰好会产生可预期结果但依赖这种“巧合”的代码在不同平台、不同缓冲模式下可能表现出截然不同的行为——例如读取位置与写入位置不一致、数据丢失或缓冲区内容被破坏。常见容易出错的场景包括用fopen(a.txt, r)打开文件后先fread读取再fwrite覆盖中间忘记用fseek(fp, 0, SEEK_CUR)之类的“空定位”强制刷新内部状态或者先写后读时忘记在两者之间重新定位。触发条件与典型误用示例文档给出的“Before”示例出错代码#include cstdio void f() { FILE *fp fopen(a.txt, r); if (!fp) return; char buf[10]; fread(buf, 1, 10, fp); fwrite(buf, 1, 10, fp); // - no seek/rewind/fflush since the read above fclose(fp); }fopen(a.txt, r)以读写模式打开文件随后fread之后直接fwrite中间没有任何定位或刷新调用因此触发IOWithoutPositioning。官方“After”示例正确代码#include cstdio void f() { FILE *fp fopen(a.txt, r); if (!fp) return; char buf[10]; fread(buf, 1, 10, fp); fseek(fp, 0, SEEK_CUR); // 空定位不移动文件位置但满足标准对读写切换的定位要求 fwrite(buf, 1, 10, fp); fclose(fp); }fseek(fp, 0, SEEK_CUR)将文件位置指针移动到当前位置——相当于“什么都不做”的定位操作但它足以满足标准要求是 read/write 模式文件在读写之间切换时的惯用写法。同理fflush(fp)也能达到同样效果。正确的修复方式小结在同一个读写流上读 → 写或写 → 读之间任选其一fseek(fp, 0, SEEK_CUR)零位移定位最常用fsetpos(fp, pos)定位到先前保存的位置rewind(fp)将位置重置到文件开头注意同时会清除错误标志fflush(fp)刷新缓冲区同样被标准视为满足读写切换的要求。底层实现cppcheck 如何跟踪 FILE*该检查器并非简单地在同一函数内找两个相邻调用而是基于一个跨语句的状态机持续跟踪每个FILE*变量的最近一次操作。Filepointer 状态结构在 lib/checkio.cpp 中每个被跟踪的FILE*变量对应一个Filepointer结构记录mode文件的打开模式只读 / 只写 / 读写 / 已关闭 / 未知lastOperation上一次操作类型枚举值为NONE、UNIMPORTANT、READ、WRITE、POSITIONING、OPEN、CLOSE、UNKNOWN_OPappend_mode/read_mode追加模式与文本/二进制读取模式供同类检查器如seekOnAppendedFile、ftellFileError使用mode_indent/op_indent记录模式与操作所在的代码块缩进层级用于处理分支与循环作用域。操作分类与判定逻辑CheckIOImpl::checkFilepointerUsage会对函数调用逐一分类lib/checkio.cppfseek、fsetpos、rewind、fflushWindows 下还包括_fseeki64→POSITIONINGfgetc、fgets、fread、fscanf、getc及 Windows 的*_s变体→READfputc、fputs、fwrite、fprintf、putc及 Windows 变体→WRITEfclose→CLOSEfopen/freopen/tmpfileWindows 下还有_wfopen/_wfreopen→OPENungetc、ftell等 →UNIMPORTANT不影响读写切换判定。判定规则集中在 lib/checkio.cppcase Filepointer::Operation::READ: if (f.mode OpenMode::CLOSED) useClosedFileError(tok); else if (f.mode OpenMode::WRITE_MODE) readWriteOnlyFileError(tok); else if (f.lastOperation Filepointer::Operation::WRITE) ioWithoutPositioningError(tok); break; case Filepointer::Operation::WRITE: if (f.mode OpenMode::CLOSED) useClosedFileError(tok); else if (f.mode OpenMode::READ_MODE) writeReadOnlyFileError(tok); else if (f.lastOperation Filepointer::Operation::READ) ioWithoutPositioningError(tok); break;即当执行读操作时若上一次操作是WRITE且中间没有POSITIONING/UNIMPORTANT重置就报告写操作同理若上一次是READ则报告。一次POSITIONING操作会把lastOperation重置从而“打断”读写连续序列。值得注意的是POSITIONING分支中若文件处于追加模式还会联动触发seekOnAppendedFile警告因为追加模式下定位无效fflush则特别处理了对输入流调用fflushOnInputStream仅按可移植性 warning 级别报告。能力边界只检查能证明的代码文档明确说明cppcheck 只沿函数内直行代码straight-line code跟踪局部FILE*变量。从源码结构看这一保守策略体现在多处当文件句柄被传给其他函数时无法判断该函数内部是否发生了定位或关闭操作cppcheck 不会猜测而是把状态置为UNKNOWN_OP未知操作全局或成员变量形式的文件句柄若其被非静态成员函数使用同样会因不确定性被重置为未知状态遇到return、continue、break或noreturn函数调用时会清空所有跟踪状态lib/checkio.cppFILE*变量被重新赋值非fopen/freopen来源时也会重置为未知。这种“宁可不报也不误报”的设计意味着只有那些在同一函数内、被局部变量持有、且读写切换路径完全可证明的代码才会命中IOWithoutPositioning。例如在 if/else 分支中分别执行读写test/testio.cpp 的测试用例if(read) fread(...); else fwrite(...);就不会被报告因为两条路径互斥cppcheck 无法证明它们在执行流上连续发生。测试用例验证该检查器在 test/testio.cpp 的fileIOwithoutPositioning()测试组中有大量覆盖可以当作“行为规范”来阅读直行代码fwrite → fread与fread → fwrite均会报告(error)分支路径if/else 各执行一种操作不报告fread → fflush → fwrite、fread → rewind → fwrite、fread → fsetpos → fwrite、fread → fseek → fwrite均不报告验证四种合法“间隔操作”都被识别关键反例fread → ftell → fwrite会报告——ftell只是查询当前位置不是定位函数不能打断读写切换序列成员函数测试issue #6452中间夹着已知的seek(writePosition)成员调用时不报告而夹着未定义的unknown(writePosition)调用时报告说明 cppcheck 能区分“已知会定位的封装”与“未知行为”。启用方式与相关检查器默认启用与 CERT 映射该检查器属于ioCheckIO模块的一部分是 cppcheck 默认启用的检查之一通常无需额外配置即可生效。它同时被映射到 CERT 规则编号lib/checkersidmapping.cppFIO39与FIO50均映射到IOWithoutPositioninglib/settings.cppIOWithoutPositioning同时被列入certCCheckers与certCppCheckers集合即使用--stdcert-c或--stdcert-c认证标准运行时也会被强制纳入检查范围。若需要在命令行显式指定检查器可以使用cppcheck --enablewarning,style,performance,portability --checkers-reportcheckers.txt 文件或目录然后在生成的checkers-report.txt中确认IOWithoutPositioning的状态也可以使用--checkers相关选项查看该检查器是否被启用。检查器的完整清单与开关方式参见 man/checkers 目录及 checkers 报告机制 的实现。同类 FILE* 跟踪检查器IOWithoutPositioning 只是 CheckIO 中同一套FILE*跟踪机制下的一个成员。文档“Related checkers”中列出的其他检查器均共享Filepointer状态机只是针对不同的非法序列useClosedFile在fclose之后继续读写已关闭文件readWriteOnlyFile对只写模式打开的文件执行读操作writeReadOnlyFile对只读模式打开的文件执行写操作seekOnAppendedFile对追加模式文件执行定位追加模式下定位无效果incompatibleFileOpen对同一文件名以冲突模式重复打开。这些检查器从 CheckIO::getErrorMessages 中可以看到完整集合它们共同构成了 cppcheck 对 C 标准库文件 I/O 未定义行为与常见误用的系统性防护。实战建议修复优先采用“空定位”fseek(fp, 0, SEEK_CUR)是不移动位置的定位调用语义清晰、可移植是读写模式流切换的标准做法rewind会同时清除错误标志并回到文件开头仅在你确实想从头开始时使用。不要用ftell充当定位ftell只查询位置不改变流状态cppcheck 的测试用例test/testio.cpp明确证明它不能打断读写切换序列。封装文件操作时让定位函数可见如果文件句柄被封装在类或函数中cppcheck 只有在能确认中间调用为已知定位操作时才不报告参见 issue #6452 的成员函数测试若无法静态证明宁可显式在读写之间调用定位函数既消除未定义行为也让静态分析结果更干净。结合同一套检查器一起修复该检查器报告的错误往往与readWriteOnlyFile、useClosedFile等同时出现建议一次处理避免遗漏同一条代码路径上的其他 I/O 未定义行为。赞分享开发工具静态分析代码质量质量保障【免费下载链接】cppcheckstatic analysis of C/C code项目地址https://gitcode.com/gh_mirrors/cpp/cppcheck点击查看免费下载相关推荐cppcheck 的 deallocret 检查器捕获“释放后返回指针”的未定义行为cppcheck 的 deallocret 检查器捕获“释放后返回指针”的未定义行为 导读 deallocret 是 cppcheck 内置的 C/C 静开发工具静态分析代码质量质量保障cppcheck invalidContainerLoop 检查器在迭代中修改容器导致的未定义行为cppcheck invalidContainerLoop 检查器在迭代中修改容器导致的未定义行为 导读 invalidContainerLoop 是 cpp开发工具静态分析代码质量质量保障Hydra 输出工作目录定制实战run/sweep 目录模式与 override_dirname 深度解析Hydra 输出工作目录定制实战run/sweep 目录模式与 override_dirname 深度解析 本篇文章围绕 Hydra 框架的输出目录work开发工具静态分析代码质量质量保障上一篇ESLint keyword-spacing 规则详解统一 JavaScript 关键字前后空格风格下一篇Vibe-Trading 实战基于 Tushare ths_member 接口获取同花顺概念板块成分股创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考