0

サンプル コードを使用して libuv について学習すると、よくわからない副作用に遭遇しました。コードは malloc() を使用してメモリを取得し、ネットワーク上のクライアントからデータを保存してから、同じデータを送り返します。次に free を使用してメモリを解放します。これは、コールバック ループを通じて何度も繰り返されます。メモリを取得するコード行は次のとおりです。

uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));

メモリを解放する行は次のとおりです。

free((char*) req->data);
free(req);

ただし、「Whats the word on the street?」のような長い文字列を入力すると、エコーされてから、短い文字列がエコーバックされた後、古い文字列の「Hi」フラグメントのような短い文字列が再び表示されます。たとえば、出力は次のようになります。

通りの言葉は何ですか?こんにちは、こんにちは、こんにちは。

メモリが解放されているため、古いフラグメントが再び表示される理由がわかりません。この件に関する私の考えは、malloc と free() について理解できないことがあるか、ライブラリにバグがあり、受信データに必要なサイズを決定する方法があり、長い文字列を使用した後に得られるということです。大きすぎたメモリブロックの一部としてガベージ。もしそうなら、それが私の以前の入力の断片であるという事実は単なる偶然です. これが考えられる理由ですか、それとも何か不足していますか? 他の情報はありますか。それを明確にするために含めるべきですか?

4

2 に答える 2

1

malloc() の実装はさまざまですが、malloc() の呼び出しは、以前に free() されたメモリのチャンクへのポインタを返すことができ、返されたメモリはゼロに設定されていないと想定しても安全です。つまり、malloc() が以前に初期化されたデータを含むデータへのポインターを提供するのは完全に正常です。

とはいえ、ここでの根本的な問題は、おそらく文字列をシリアル化する方法のアーティファクトである、終端されていない文字列であると思われます。たとえば、クライアントから単に strlen(str) バイトを書き込んでいる場合、NULL を書き込んでいるわけではありません。その結果、サーバーがメッセージを受信すると、終了していない文字列が含まれます。これが文字列を渡す方法であり、それを通常の null で終わる文字列として扱うことを計画している場合、サーバーは、文字列と追加の NULL 文字を収容するのに十分な大きさのバッファーにデータをコピーする必要があります。

では、なぜ過去のメッセージの断片を見ているのでしょうか? おそらくばかげた運。これが非常に単純なアプリである場合、malloc() が前の要求と重複するメモリのチャンクを返す可能性は非常に高くなります。

では、なぜこのようにきれいな出力が得られるのでしょうか?大量のデータの文字化けや、文字列操作のセグメンテーション違反が無限に続くのではないでしょうか? 繰り返しますが、運が悪いです。カーネルが最初にアプリケーションにメモリのページを与えるとき、最初にページアウトがゼロになることに注意してください (これはセキュリティ上の理由から行われます)。そのため、文字列を終了していなくても、文字列が存在するヒープ メモリのページは、比較的初期のゼロアウト状態にある可能性があります。

于 2013-03-29T23:36:41.170 に答える
1

uv_write_t ※reqは送受信するデータではありません。これは、書き込み要求へのハンドルのようなものです。

どちらも req->data ではありません。これは、任意の個人データへのポインターです。たとえば、接続に関連するデータを渡したい場合に使用できます。

実際のペイロード データは、書き込みバッファー (uv_buf_t) を介して送信され、読み取り要求が処理されたときに割り当てられるバッファーに受信されます。read 関数が alloc パラメータを必要とするのはそのためです。その後、そのバッファは read コールバックに渡されます。

req->data の解放は、'data' が (あなたによって) malloc されたプライベート データ (通常は構造体) を指していることを前提としています。

経験則として、ソケットは uv_xxx_t で表され、読み取りと書き込みは「リクエスト」構造を使用します。サーバー (典型的な uv の使用例) を作成すると、接続数がわからないため、すべてが動的に割り当てられます。

生活を楽にするために、ペア (オープン/クローズまたは開始/完了) について考えることができます。したがって、新しい接続を受け入れるときに、サイクルを開始してクライアントを割り当てます。その接続を閉じると、解放されます。書き込み時には、要求とペイロード データ バッファーを割り当てます。書き終わったら、それらを解放します。読み取り時に読み取り要求を割り当て、読み取りが完了したとき (およびペイロード データをコピーしたとき) にペイロード データ バッファーが舞台裏で (alloc コールバックを介して) 割り当てられ、両方を解放します。

これらの malloc/free のペアをすべて使用せずに作業を完了する方法はありますが (これはパフォーマンス面で輝かしいものではありません)、初心者の場合は uv ドキュメントに同意します。間違いなく malloc/free ルートから始める必要があります。アイデアを提供するために:私はすべてを1万または10万の接続に事前に割り当てますが、それにはいくつかの管理とトリックが伴います。たとえば、割り当てコールバックを偽って、事前に割り当てられたバッファーの1つを割り当てるだけです。

推測するように求められた場合、malloc/free を回避することは、いつでも 5k ~ 10k の接続をはるかに超えた場合にのみ価値があることをお勧めします。

于 2016-04-22T08:02:28.080 に答える