2016-04-27 179 views
0

我正在爲輸出流實現自己的streambuffer。基本上它是一個向量流緩衝區,每次溢出函數都會將緩衝區重新分配爲兩倍大。同步功能將所有數據寫入由文件描述符fd指定的設備。爲C++ ostream自定義streambuffer

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; 
} 

這個實現有什麼問題嗎?我看到很多文章通常覆蓋syncoverflow是必需的。

回答

1

的問題,現在看來,是你的對象dstd::cout之前被銷燬,從而爲破壞全局對象,其中包括沖洗緩衝區的最後通話,並採取。安迪的main()結束後(記住這是一個全球性的對象)嘗試對不再存在的對象執行操作。您的緩衝區對象絕對應該超過您關聯的流。

在你的程序中使用這種方法的一種方法是將d製作成一個永遠不會刪除的指針。或者,您可以保留您的本地對象,但請撥打std::cout.flush(),然後在超出範圍之前將cout的緩衝區分配給其他內容(即使是nullptr

在對您的程序進行測試時(以及在發現問題之前),我做出了一些對我來說很有意義的小改動。例如,在成功寫入描述符後,您可以簡單地使用bump(ret)(您已經知道ret!=-1,因此可以安全使用)。

我沒有做出的其他修改,但您可以考慮的是,由構造函數本身設置描述符,使析構函數關閉懸掛描述符,並且可能會改變面向C的動態分配malloc()/realloc()面向C++的std::vector

說到分配,你在使用realloc()時犯了一個很常見的錯誤。如果重新分配失敗,realloc()將保持原始指針不變,並通過返回空指針來指示失敗。由於您使用相同的指針來獲取返回值,因此可能會失去對仍然分配的內存的引用。因此,如果您根本不能使用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); 
}