原文链接:

FIO50-CPP. Do not alternately input and output from a file stream without an intervening positioning call


C++ 标准,[filebuf],段落 2 [ISO/IEC 14882-2014],叙述如下:

受类 basic_filebuf<charT, traits> 的对象控制的读写限制和 C 标准库 FILES 是一样

C 标准,分条款 7.19.5.3,段落 6 [ISO/IEC 9899:1999],当需要对一个打开的文件流同时读写时,遵循以下限制:

当一个文件正被以更新的模式打开时……,相关数据流可能同时执行输入和输出操作。然而,当没有 fflush 函数或获取文件位置函数(fseekfsetposrewind)的介入调用(intervening call),输出不应该直接接着输入,除非输入操作遇到了文件结束符。

所以,下述情景可能导致 未定义行为

  • 如果文件并不处于结束符,那么在输出到数据流操作后紧接着从该数据流输入,而没有 std::basic_filebuf<T>::seekoff() 的介入调用

  • 如果文件并不处于结束符,那么在从数据流输入后紧接着输出到该数据流,而没有 std::basic_filebuf<T>::seekoff() 的介入调用

std::basic_filebuf<T>::seekoff() 之外,没有其他函数能保证行为和调用 C 标准库的文件定位函数一致,或 std::fflush()

对一个文件流调用 std::basic_ostream<T>::seekp()std::basic_istream<T>::seekg() 最终还是会调用到 std::basic_filebuf<T>::seekoff() . 考虑到 std::basic_iostream<T> 同时从继承于 std::basic_ostream<T>std::basic_istream<T>, 并且 std::fstream 继承于 std::basic_iostream, 用来保证文件流在被后续 I/O 操作前处于合法状态的任何函数调用都是可接受的。

不合规的代码示例

这个不合规的例子在文件末尾附加了数据,然后从同一个文件里读取。然而,由于在格式化输出和输入调用之间没有介入位置的调用,该行为是 未定义 的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <fstream>
#include <string>

void f(const std::string &fileName) {
std::fstream file(fileName);
if (!file.is_open()) {
// Handle error
return;
}

file << "Output some data";
std::string str;
file >> str;
}

合规方案

在这个合规方案中, std::basic_istream<T>::seekg() 函数在输入和输出之间被调用,消除了 未定义行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <fstream>
#include <string>

void f(const std::string &fileName) {
std::fstream file(fileName);
if (!file.is_open()) {
// Handle error
return;
}

file << "Output some data";

std::string str;
file.seekg(0, std::ios::beg);
file >> str;
}

Risk Assessment

Alternately inputting and outputting from a stream without an intervening flush or positioning call is undefined behavior.

Rule Severity Likelihood Remediation Cost Priority Level
FIO50-CPP Low Likely Medium P6 L2

Automated Detection

Tool Version Checker Description
Axivion Bauhaus Suite 7.2.0 CertC++-FIO50
Helix QAC 2021.2 C++4711, C++4712, C++4713
Parasoft C/C++test 2021.1 CERT_CPP-FIO50-a Do not alternately input and output from a stream without an intervening flush or positioning call
Polyspace Bug Finder R2021b CERT C++: FIO50-CPP Checks for alternating input and output from a stream without flush or positioning call (rule fully covered)

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

This rule supplements FIO39-C. Do not alternately input and output from a stream without an intervening flush or positioning call.