为 C++ ostream 自定义流缓冲区
Customize streambuffer for C++ ostream
我正在为输出流实现我自己的流缓冲区。基本上它是一个类似向量的流缓冲区,每次溢出函数都会简单地将缓冲区重新分配到两倍大。 sync 函数会将所有数据写出到文件描述符 fd
指定的设备。
class MyStreamBuf : public ::std::streambuf {
constexpr static size_t INIT_BUFFER_SIZE {1024};
public:
MyStreamBuf();
~MyStreamBuf();
void fd(const int);
int sync() override;
int_type overflow(int_type ch = traits_type::eof()) override;
private:
int _fd {-1};
size_t _size;
char_type* _base;
void _resize(const size_t);
};
MyStreamBuf::MyStreamBuf() {
_size = INIT_BUFFER_SIZE;
_base = static_cast<char_type*>(malloc(_size * sizeof(char_type)));
setp(_base, _base + _size - 1); // -1 to make overflow easier.
}
// Destructor.
MyStreamBuf::~MyStreamBuf() {
::free(_base);
}
// Procedure: fd
// Change the underlying device.
void MyStreamBuf::fd(const int fd) {
_fd = fd;
}
// Procedure: _resize
// Resize the underlying buffer to fit at least "tgt_size" items of type char_type.
void MyStreamBuf::_resize(const size_t tgt_size) {
// Nothing has to be done if the capacity can accommodate the file descriptor.
if(_size >= tgt_size) return;
// Adjust the cap to the next highest power of 2 larger than num_fds
for(_size = (_size ? _size : 1); _size < tgt_size; _size *= 2);
// Adjust and reset the memory chunk.
_base = static_cast<char_type*>(::realloc(_base, _size*sizeof(char_type)));
setp(_base, _base + _size - 1); // -1 to make overflow easier.
}
int MyStreamBuf::sync() {
int res = 0;
::std::ptrdiff_t remain = pptr() - pbase();
while(remain) {
issue_write:
auto ret = ::write(_fd, pptr() - remain, remain);
if(ret == -1) {
if(errno == EINTR) {
goto issue_write;
}
else if(errno == EAGAIN) {
break;
}
else {
res = -1;
break;
}
}
remain -= ret;
}
if(remain) {
::memcpy(pbase(), pptr() - remain, remain*sizeof(char_type));
}
pbump(pbase() + remain - pptr());
return res;
}
typename MyStreamBuf::int_type MyStreamBuf::overflow(int_type ch) {
assert(traits_type::eq_int_type(ch, traits_type::eof()) == false);
_resize(_size * 2);
return ch;
}
但是我在用我自己的缓冲区替换 cout
时遇到段错误。在GDB上苦苦挣扎,我找不到错误在哪里。
// Function: main
int main() {
auto fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
MyStreamBuf d;
d.fd(fd);
::std::cout.rdbuf(&d);
::std::cout << 1 << " " << 2 << ::std::endl;
close(fd);
return 0;
}
这个实现有什么问题吗?我看到许多文章通常会覆盖 sync
和 overflow
是必需的。
问题似乎是您的对象 d
在 std::cout
之前被销毁,因此最终调用销毁全局对象,包括刷新缓冲区,并在之后发生main()
的末尾(记住它是一个全局对象),尝试对不再存在的 streambuf
对象执行操作。您的缓冲区对象绝对应该比您关联的流长。
在你的程序中使用它的一种方法是将 d
变成一个你永远不会删除的指针。或者,您可以在使用时保留本地对象,但调用 std::cout.flush()
,然后将 cout
的缓冲区分配给其他对象(甚至 nullptr
)before 超出范围。
在测试您的程序时(在我发现问题之前),我做了一些对我有意义的小改动。例如,成功写入描述符后,您可以简单地 bump(ret)
(您已经知道 ret!=-1
,因此可以安全使用)。
我没有做但你可以考虑的其他更改是让构造函数本身设置描述符,让析构函数关闭悬空描述符,并且可能更改面向 C 的动态分配 malloc()
/realloc()
/free()
到面向C++的std::vector
.
说到分配,您在使用 realloc()
时犯了一个很常见的错误。如果重新分配失败,realloc()
将保持原始指针不变,并通过 returning 一个空指针来表示失败。由于您使用相同的指针来获取 return 值,因此您可能会丢失对仍分配的内存的引用。所以,如果你根本不能使用 C++ 容器而不是 C 指针,你应该将你的代码更改为更像这样的东西:
char *newptr;
newptr=static_cast<char *>(realloc(ptr, newsize));
if(newptr)
ptr=newptr;
else {
// Any treatment you want. I wrote some fatal failure code, but
// you might even prefer to go on with current buffer.
perror("ralloc()");
exit(1);
}
我正在为输出流实现我自己的流缓冲区。基本上它是一个类似向量的流缓冲区,每次溢出函数都会简单地将缓冲区重新分配到两倍大。 sync 函数会将所有数据写出到文件描述符 fd
指定的设备。
class MyStreamBuf : public ::std::streambuf {
constexpr static size_t INIT_BUFFER_SIZE {1024};
public:
MyStreamBuf();
~MyStreamBuf();
void fd(const int);
int sync() override;
int_type overflow(int_type ch = traits_type::eof()) override;
private:
int _fd {-1};
size_t _size;
char_type* _base;
void _resize(const size_t);
};
MyStreamBuf::MyStreamBuf() {
_size = INIT_BUFFER_SIZE;
_base = static_cast<char_type*>(malloc(_size * sizeof(char_type)));
setp(_base, _base + _size - 1); // -1 to make overflow easier.
}
// Destructor.
MyStreamBuf::~MyStreamBuf() {
::free(_base);
}
// Procedure: fd
// Change the underlying device.
void MyStreamBuf::fd(const int fd) {
_fd = fd;
}
// Procedure: _resize
// Resize the underlying buffer to fit at least "tgt_size" items of type char_type.
void MyStreamBuf::_resize(const size_t tgt_size) {
// Nothing has to be done if the capacity can accommodate the file descriptor.
if(_size >= tgt_size) return;
// Adjust the cap to the next highest power of 2 larger than num_fds
for(_size = (_size ? _size : 1); _size < tgt_size; _size *= 2);
// Adjust and reset the memory chunk.
_base = static_cast<char_type*>(::realloc(_base, _size*sizeof(char_type)));
setp(_base, _base + _size - 1); // -1 to make overflow easier.
}
int MyStreamBuf::sync() {
int res = 0;
::std::ptrdiff_t remain = pptr() - pbase();
while(remain) {
issue_write:
auto ret = ::write(_fd, pptr() - remain, remain);
if(ret == -1) {
if(errno == EINTR) {
goto issue_write;
}
else if(errno == EAGAIN) {
break;
}
else {
res = -1;
break;
}
}
remain -= ret;
}
if(remain) {
::memcpy(pbase(), pptr() - remain, remain*sizeof(char_type));
}
pbump(pbase() + remain - pptr());
return res;
}
typename MyStreamBuf::int_type MyStreamBuf::overflow(int_type ch) {
assert(traits_type::eq_int_type(ch, traits_type::eof()) == false);
_resize(_size * 2);
return ch;
}
但是我在用我自己的缓冲区替换 cout
时遇到段错误。在GDB上苦苦挣扎,我找不到错误在哪里。
// Function: main
int main() {
auto fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
MyStreamBuf d;
d.fd(fd);
::std::cout.rdbuf(&d);
::std::cout << 1 << " " << 2 << ::std::endl;
close(fd);
return 0;
}
这个实现有什么问题吗?我看到许多文章通常会覆盖 sync
和 overflow
是必需的。
问题似乎是您的对象 d
在 std::cout
之前被销毁,因此最终调用销毁全局对象,包括刷新缓冲区,并在之后发生main()
的末尾(记住它是一个全局对象),尝试对不再存在的 streambuf
对象执行操作。您的缓冲区对象绝对应该比您关联的流长。
在你的程序中使用它的一种方法是将 d
变成一个你永远不会删除的指针。或者,您可以在使用时保留本地对象,但调用 std::cout.flush()
,然后将 cout
的缓冲区分配给其他对象(甚至 nullptr
)before 超出范围。
在测试您的程序时(在我发现问题之前),我做了一些对我有意义的小改动。例如,成功写入描述符后,您可以简单地 bump(ret)
(您已经知道 ret!=-1
,因此可以安全使用)。
我没有做但你可以考虑的其他更改是让构造函数本身设置描述符,让析构函数关闭悬空描述符,并且可能更改面向 C 的动态分配 malloc()
/realloc()
/free()
到面向C++的std::vector
.
说到分配,您在使用 realloc()
时犯了一个很常见的错误。如果重新分配失败,realloc()
将保持原始指针不变,并通过 returning 一个空指针来表示失败。由于您使用相同的指针来获取 return 值,因此您可能会丢失对仍分配的内存的引用。所以,如果你根本不能使用 C++ 容器而不是 C 指针,你应该将你的代码更改为更像这样的东西:
char *newptr;
newptr=static_cast<char *>(realloc(ptr, newsize));
if(newptr)
ptr=newptr;
else {
// Any treatment you want. I wrote some fatal failure code, but
// you might even prefer to go on with current buffer.
perror("ralloc()");
exit(1);
}