【发布时间】:2021-04-28 17:29:51
【问题描述】:
目标
对于旧版 C++11 程序具有无缓冲 I/O 和禁用内核页面缓存。此功能必须按需(通过可执行参数)。这个想法是为了减少 I/O 操作的内存开销,而不管性能如何。我不确定这是实现这一目标的正确方法......
我的尝试
代码库非常大,std::ifstream 和 std::ofstream 的大量使用分布在不同的二进制文件/库中,我的目标是实现一个从 std::filebuf 派生的类,它依赖于 CI/O 特性 (@987654325 @、open() 这样我就可以传递O_DIRECT 标志等),并使用继承方法std::basic_streambuf<CharT,Traits>* std::basic_ios<CharT,Traits>::rdbuf(std::basic_streambuf<CharT,Traits>*) 将其传递给std::ifstream 对象(目前仅输入)。
问题
问题在于std::ifstream 对象实际上似乎有两个内部缓冲区。查看代码以了解我的实验(可能仍然存在一些明显的错误)。
我的文件缓冲区
// FileBuf.h
class FileBuf : public std::filebuf {
public:
FileBuf();
virtual ~FileBuf();
virtual std::filebuf* open(const char* filename, std::ios_base::openmode mode);
virtual std::filebuf* open(const std::string filename, std::ios_base::openmode mode);
virtual bool is_open() const;
virtual std::filebuf* close();
virtual std::streambuf* setbuf(char_type* s, std::streamsize n);
virtual int_type overflow(int c = traits_type::eof());
virtual FileBuf::int_type underflow();
virtual int sync();
private:
int _fd;
FILE * _fp;
char _buff[1]; // minimal size
};
// FileBuf.cpp
FileBuf::FileBuf()
: std::filebuf(), _fd(0), _fp(NULL)
{}
FileBuf::~FileBuf() {
close(); // RAII
}
std::filebuf* FileBuf::open(const char* filename, std::ios_base::openmode mode) {
std::cout << "open(const char*, ..): filename=" << filename << ", mode=" << mode << std::endl;
// not finished, need to handle all modes
int flags = O_RDONLY;
mode_t fmode = S_IRUSR;
std::string smode = "r";
_fd = ::open(filename, flags, fmode);
_fp = ::fdopen(_fd, smode.c_str());
return _fp != NULL ? this : nullptr;
}
std::filebuf* FileBuf::open(const std::string filename, std::ios_base::openmode mode) {
std::cout << "open(const std::string, ..): filename=" << filename << ", mode=" << mode << std::endl;
return open(filename.c_str(), mode);
}
std::streambuf* FileBuf::setbuf(char_type* s, std::streamsize n) {
return this;
}
bool FileBuf::is_open() const {
return (_fp != NULL);
}
std::filebuf* FileBuf::close() {
std::cout << "close()" << std::endl;
if (_fp) {
if (std::fclose(_fp) == 0) {
return this;
}
}
return nullptr;
}
FileBuf::int_type FileBuf::overflow(int_type c) {
std::cout << "overflow()" << std::endl;
if (traits_type::eq_int_type(c, traits_type::eof())) {
return (sync() == 0) ? traits_type::not_eof(c) : traits_type::eof();
} else {
return ((std::fputc(c, _fp) != EOF) ? traits_type::not_eof(c) : traits_type::eof());
}
}
FileBuf::int_type FileBuf::underflow()
{
std::cout << "underflow(): _fp=" << _fp << std::endl;
if (gptr() == NULL || gptr() >= egptr()) {
int gotted = fgetc(_fp);
if (gotted == EOF) {
return traits_type::eof();
} else {
*_buff = gotted;
setg(_buff, _buff, _buff + 1);
return traits_type::to_int_type(*_buff);
}
} else {
return traits_type::to_int_type(*_buff);
}
}
int FileBuf::sync()
{
std::cout << "sync()" << std::endl;
return (std::fflush(_fp) == 0) ? 0 : -1;
}
客户端代码
std::string buff(1024, '\0');
std::ifstream ifs;
FileBuf fileBuf;
ifs.std::istream::rdbuf(&fileBuf); // file buf passed here
std::cout << "rdbuf()=" << static_cast<void*>(ifs.rdbuf()) << ", istream.rdbuf()=" << static_cast<void*>(ifs.std::istream::rdbuf()) << ", &fileBuf=" << static_cast<void*>(&fileBuf) << std::endl;
ifs.open("data/test1/delta");
ifs.read(&buff[0], 1024);
输出
rdbuf()=0x7fffffffdb10, istream.rdbuf()=0x7fffffffd9f0, &fileBuf=0x7fffffffd9f0
underflow(): _fp=0
// !! SEGFAULT !!
如输出所示,rdbuf() 的两种风格不引用同一个内部缓冲区,并且FileBuf::open 从未被调用,正如std::basic_ifstream<CharT,Traits>::open 中所指定的那样:
有效调用rdbuf()->open(filename, mode | ios_base::in)
我了解发生了什么:正在调用 std::basic_ifstream::rdbuf 返回的内部缓冲区对象,而不是来自 std::basic_ios<CharT,Traits>::rdbuf 的缓冲区对象,但我仍然不知道如何获得我想要的行为。
我想避免 - 不惜一切代价 - 将所有 std::ifstream 引用替换为它的自定义实现,因为这意味着要替换所有当前声明中的类型。
注意:我正在使用 gcc 和 libstdc++ 进行编译。
【问题讨论】:
-
出于好奇,目标背后的动机是什么?你能从 3 个代码 sn-ps 中制作一个大代码 sn-p,将
int main()添加到第 3 个代码 sn-p 并添加缺少的#includes 吗?当代码易于复制、运行和测试时,它会很有帮助... |at all costs- to replace all std::ifstream references with a custom implementation但你必须做某事。 添加另一个FileBuf到每个std::ifstream然后再添加另一个...rdbuf(&filebuf)调用到每个构造函数然后替换声明不是更难吗?
标签: c++ linux fstream file-pointer filebuf