0

出力ストリーム用に独自のストリームバッファを実装しています。基本的に、これはベクトルのようなストリーム バッファであり、オーバーフロー関数が毎回単純にバッファを 2 倍の大きさに再割り当てします。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ただし、を自分のバッファーに置き換えると、segfault が発生します。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、必須であるのを見てきました。

4

1 に答える 1

1

問題は、オブジェクトdが の前std::coutに破棄されることです。そのため、グローバル オブジェクトを破棄するための最終呼び出し (フラッシュ バッファーが含まれ、main()(グローバル オブジェクトであることを思い出してください) の終了後に取り除かれます) が操作を実行しようとします。もはや現存しないstreambufオブジェクトに。バッファ オブジェクトは、関連付けたストリームよりも確実に存続する必要があります。

これをプログラムにd組み込む 1 つの方法は、絶対に削除しないポインターにすることです。または、ローカルオブジェクトを使用したままにしておくこともできますが、 を呼び出してから、スコープ外に出るにのバッファを別のもの ( であっても)にstd::cout.flush()割り当てます。coutnullptr

あなたのプログラムをテストしている間 (そして問題が見つかる前に)、私は自分にとって意味のある小さな変更を加えました。たとえば、記述子への書き込みに成功した後は、簡単に実行できますbump(ret)(既にわかっているret!=-1ので、安全に使用できます)。

私が行っていないが考慮できるその他の変更は、コンストラクター自体によって記述子を設定し、デストラクタにぶら下がっている記述子を閉じさせ、おそらく動的割り当てを C 指向の // から C++ 指向に変更するmalloc()ことです。realloc()free()std::vector

割り当てといえば、 を使用するときに非常によくある間違いを犯しましたrealloc()。再割り当てが失敗した場合はrealloc()、元のポインターをそのまま保持し、null ポインターを返すことで失敗を通知します。同じポインタを使用して戻り値を取得するため、まだ割り当てられているメモリへの参照が失われる危険があります。したがって、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);
}
于 2016-04-27T23:08:34.560 に答える