2016-04-27 12 views
0

Ich bin meine eigene streambuffer für Ausgabestream implementieren. Im Grunde ist es ein vektorartiger Strompuffer, in dem die Überlauffunktion den Puffer einfach zweimal zuweist. Die Synchronisierungsfunktion schreibt alle Daten auf das Gerät, das durch einen Dateideskriptor fd festgelegt wurde.Anpassen streambuffer für C++ ostream

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

aber ich bin immer segfault während der cout mit meinem eigenen Puffer zu ersetzen. Ich konnte nicht finden, wo der Fehler ist, nachdem ich mit GDB gekämpft habe.

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

Ist etwas falsch mit dieser Implementierung? Ich sah viele Artikel, die in der Regel Vorrang haben sync und overflow sind erforderlich.

Antwort

1

Das Problem, so scheint es, ist, dass Ihr Objekt d vor std::cout zerstört wird, und somit die letzten Anrufe für das globale Objekt zerstörenden, die Spülung Puffer umfassen, und dass palce nach dem Ende der main() nehmen (nicht vergessen, es ist ein global ist Objekt), versuchen, Operationen an einem nicht mehr existenten Objekt streambuf durchzuführen. Ihr Pufferobjekt sollte definitiv den Strom überleben, dem Sie es zuordnen.

Eine Möglichkeit, dies in Ihrem Programm zu haben, besteht darin, d zu einem Zeiger zu machen, den Sie niemals löschen werden. Alternativ können Sie Ihr lokales Objekt so beibehalten, wie Sie es verwendet haben, aber rufen Sie std::cout.flush(), und weisen Sie dann cout Puffer auf etwas anderes (auch nullptr) vor außerhalb des Geltungsbereichs zuweisen.

Während des Testens mit Ihrem Programm (und bevor ich das Problem fand), machte ich kleine Änderungen, die für mich sinnvoll waren. Zum Beispiel, nachdem Sie erfolgreich in den Deskriptor schreiben, können Sie einfach bump(ret) (Sie wissen bereits, dass ret!=-1, so ist es sicher zu verwenden).

Andere Änderungen, die ich nicht machen, aber die Sie in Erwägung ziehen könnten, ist der Deskriptor durch den Konstruktor selbst gesetzt haben, die destructor einen baumelnden Descriptor schließen zu müssen, und vielleicht die dynamische Zuordnung ändern von C-orientierten malloc()/realloc()/free() nach C++ - orientiert std::vector.

Apropos Zuteilung, Sie haben einen sehr häufigen Fehler gemacht, wenn Sie realloc() verwenden. Wenn die Neuzuweisung fehlschlägt, behält den ursprünglichen Zeiger intakt und signalisiert den Fehler, indem ein Nullzeiger zurückgibt. Da Sie denselben Zeiger verwenden, um den Rückgabewert abzurufen, riskieren Sie den Verweis auf einen noch zugewiesenen Speicher zu verlieren. Also, wenn Sie überhaupt keine C++ - Container anstelle von C-Zeigern verwenden können, sollten Sie den Code in etwas anderes ändern:

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