39

私は非常に古い学校のプログラマーによって書かれたコードを持っています:-)。それはこのようなものになります

typedef struct ts_request
{ 
  ts_request_buffer_header_def header; 
  char                         package[1]; 
} ts_request_def; 

ts_request_def* request_buffer = 
malloc(sizeof(ts_request_def) + (2 * 1024 * 1024));

プログラマは基本的にバッファ オーバーフローの概念に取り組んでいます。私はコードが危険に見えることを知っています。私の質問は次のとおりです。

  1. malloc は常にメモリの連続ブロックを割り当てますか? このコードでは、ブロックが連続していない場合、コードは大きな失敗をするためです。

  2. free(request_buffer)実行すると、malloc ie によって割り当てられたすべてのバイトが解放されますかsizeof(ts_request_def) + (2 * 1024 * 1024)、それとも構造体のサイズのバイトのみが解放されますかsizeof(ts_request_def)

  3. このアプローチに明らかな問題はありますか。これについて上司と話し合う必要があり、このアプローチの抜け穴を指摘したいと思います。

4

14 に答える 14

53

あなたの点数に答えるために。

  1. はい。
  2. すべてのバイト。malloc/free は、オブジェクトのタイプを認識したり、気にしたりしません。サイズだけです。
  3. これは厳密には未定義の動作ですが、多くの実装でサポートされている一般的なトリックです。他の代替手段については、以下を参照してください。

最新の C 標準である ISO/IEC 9899:1999 (非公式には C99) では、柔軟な配列メンバーが許可されています。

この例は次のようになります。

int main(void)
{       
    struct { size_t x; char a[]; } *p;
    p = malloc(sizeof *p + 100);
    if (p)
    {
        /* You can now access up to p->a[99] safely */
    }
}

この標準化された機能により、質問で説明した一般的ではあるが標準ではない実装拡張機能の使用を避けることができました。厳密に言えば、柔軟でない配列メンバーを使用し、その境界を超えてアクセスすることは未定義の動作ですが、多くの実装ではそれが文書化され、推奨されています。

さらに、gccでは拡張機能として長さゼロの配列を使用できます。長さゼロの配列は標準 C では違法ですが、C99 が柔軟な配列メンバーを提供する前に gcc がこの機能を導入しました。

コメントへの返信で、以下のスニペットが技術的に未定義の動作である理由を説明します。私が引用したセクション番号は、C99 (ISO/IEC 9899:1999) を参照しています。

struct {
    char arr[1];
} *x;
x = malloc(sizeof *x + 1024);
x->arr[23] = 42;

まず、6.5.2.1#2 は a[i] が (*((a)+(i))) と同一であることを示しているため、x->arr[23] は (*((x->arr)+( 23))))。現在、6.5.6#8 (ポインターと整数の追加) は次のように述べています。

「ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の1つ後ろを指している場合、評価はオーバーフローを生成しません。そうでない場合、動作は未定義です。」

このため、x->arr[23] は配列内にないため、動作は未定義です。malloc() は配列が拡張されたことを意味するため、問題ないと考えるかもしれませんが、厳密にはそうではありません。有益な附属書 J.2 (未定義の動作の例を一覧表示) は、例を使用してさらに明確にします。

配列の添字は、オブジェクトが指定された添字で明らかにアクセス可能であっても (宣言 int a[4][5] が与えられた左辺値式 a[1][7] のように) 範囲外です (6.5.6)。

于 2009-03-09T08:40:27.480 に答える
10

3-これは、構造体の最後に動的配列を割り当てるための非常に一般的なCのトリックです。別の方法は、構造体にポインタを置き、配列を個別に割り当てることです。また、配列を解放することも忘れないでください。サイズが2mbに固定されているのは少し珍しいようですが。

于 2009-03-09T07:41:12.380 に答える
8

これは標準的な C のトリックであり、他のバッファほど危険ではありません。

あなたが「非常に古い学校のプログラマー」よりも賢いことを上司に見せようとしている場合、このコードはあなたのケースではありません. 古い学校は必ずしも悪いわけではありません。「古い学校」の男はメモリ管理について十分に知っているようです;)

于 2009-03-09T08:23:16.820 に答える
3

#3に関しては、それ以上のコードがなければ答えるのは難しいです。それがたくさん起こらない限り、私はそれについて何も悪いことは見ていません。つまり、常に2MBのメモリチャンクを割り当てたくないということです。また、たとえば2kしか使用しない場合など、不必要にそれを実行したくはありません。

何らかの理由でそれが気に入らないという事実は、それに反対したり、完全に書き直したりするのに十分ではありません。私は使用法を注意深く見て、元のプログラマーが何を考えていたかを理解しようとし、このメモリを使用するコードのバッファオーバーフロー(workmad3が指摘したように)を注意深く調べます。

あなたが見つけるかもしれない多くの一般的な間違いがあります。たとえば、コードはmalloc()が成功したことを確認するためにチェックしますか?

于 2009-03-09T07:28:08.903 に答える
3

エクスプロイト(質問3)は、実際には、この構造に向けたインターフェース次第です。コンテキストでは、この割り当ては理にかなっている可能性があり、それ以上の情報がなければ、それが安全であるかどうかを判断することは不可能です。
しかし、構造体よりも大きなメモリの割り当てに問題がある場合、これは決して悪いC設計ではありません(古い学校だとは言えません...;))
ここでの最後の注意点- char [1]は、終了するNULLが常に宣言された構造体に含まれることを意味します。つまり、バッファーには2 * 1024 * 1024文字が含まれる可能性があり、NULLを「+1」で説明する必要はありません。小さな偉業のように見えるかもしれませんが、私はただ指摘したかっただけです。

于 2009-03-09T07:37:39.440 に答える
3

はい。mallocは単一のポインターのみを返します-リクエストを満たすために複数の不連続なブロックを割り当てたことをリクエスターにどのように伝えることができますか?

于 2009-03-11T00:47:23.607 に答える
3

私はこのパターンを頻繁に見たり使用したりしてきました。

その利点は、メモリ管理を簡素化し、メモリ リークのリスクを回避できることです。必要なのは、malloc されたブロックを解放することだけです。セカンダリ バッファーを使用すると、空きが 2 つ必要になります。ただし、この操作をカプセル化するためにデストラクタ関数を定義して使用する必要があります。これにより、セカンダリ バッファに切り替えたり、構造を削除するときに実行する操作を追加したりするなど、いつでもその動作を変更できます。

配列要素へのアクセスもわずかに効率的ですが、最近のコンピューターではそれほど重要ではありません。

メモリ アラインメントが異なるコンパイラで頻繁に変更される場合でも、コードは正しく機能します。

私が目にする唯一の潜在的な問題は、コンパイラがメンバー変数のストレージの順序を変更する場合です。このトリックでは、パッケージ フィールドがストレージの最後に残る必要があるためです。C標準が順列を禁止しているかどうかはわかりません。

また、割り当てられたバッファのサイズは、必要なサイズよりも大きくなる可能性が高く、追加のパディング バイトがある場合は少なくとも 1 バイト大きくなることに注意してください。

于 2009-03-09T08:29:48.847 に答える
2

この一般的な C のトリックは、この StackOverflow の質問でも説明されています (solaris の dirent 構造体のこの定義を誰かが説明できますか?)

于 2009-03-13T07:37:43.107 に答える
2

一般的ではないことを付け加えたいと思いますが、Windows API はそのような用途に満ちているため、標準的な方法と呼ぶこともできます。

たとえば、非常に一般的な BITMAP ヘッダー構造を確認してください。

http://msdn.microsoft.com/en-us/library/aa921550.aspx

最後の RBG クワッドは、まさにこの手法に依存する 1 サイズの配列です。

于 2009-03-09T08:38:42.773 に答える
2

3 番目の質問への回答です。

free常に一度に割り当てられたすべてのメモリを解放します。

int* i = (int*) malloc(1024*2);

free(i+1024); // gives error because the pointer 'i' is offset

free(i); // releases all the 2KB memory
于 2009-03-13T07:31:54.167 に答える
1

質問 1 と 2 の答えは「はい」です

醜さについて (質問 3)、割り当てられたメモリを使ってプログラマは何をしようとしているのでしょうか。

于 2009-03-09T07:22:48.307 に答える