web-dev-qa-db-ja.com

なぜ、またはいつ、Cでメモリを動的に割り当てる必要があるのですか?

動的メモリ割り当ては、Cプログラミングにおいて非常に重要なトピックです。しかし、これにより何ができるのか、なぜそれが必要なのかについて、私はうまく説明できませんでした。

変数と構造体を宣言するだけで、malloc()を使用する必要はありませんか?

補足として、次の違いは何ですか?

ptr_one = (int *)malloc(sizeof(int));

そして

int *ptr_one = malloc(sizeof(int));
23
user2517777

次の場合、動的メモリを使用する必要があります。

  • コンパイル時に使用するメモリの最大量を決定することはできません。
  • veryラージオブジェクトを割り当てたい。
  • 上限サイズを固定せずにデータ構造(コンテナー)を構築したい。

コンパイル時にどれだけのメモリを確保する必要があるかは常にわかりません。ファイル内のレコード数が固定されていないデータファイル(たとえば、時系列の温度)を処理するとします。レコードは10個から100000個まで可能です。すべてのデータをメモリに読み込んで処理する場合、ファイルを読み取るまで、割り当てるメモリ量はわかりません。最初の値がレコード数になるようにファイルが構造化されている場合、次のようなことができます。

size_t recs = 0;
double *temps = NULL;

FILE *fp = fopen ( filename, "r" );
if ( fp )
{
  if ( fscanf( fp, "%zu", &recs ) == 1 )
  {
    temps = malloc( sizeof *temps * recs );
    if ( temps )
    {
      // read contents of file into temps
    }
  }
}

very大きなオブジェクトを割り当てる必要がある場合があります。

int ginormous[1000][1000][1000];

4バイト整数を想定すると、この配列には4GBが必要になります。残念ながら、スタックフレーム(ほとんどのアーキテクチャでローカル変数が保持されている)はそれよりもはるかに小さい傾向があるため、その量のメモリを割り当てようとすると、ランタイムエラーが発生する可能性があります(通常はそうなります)。動的メモリプール(別名、ヒープ)は通常、スタックよりもはるか大きく、1つのスタックフレームよりはるかに小さくなります。だから不愉快なもののためにあなたは次のようなものを書く必要があります

int (*ginormous)[1000][1000] = malloc( sizeof *ginormous * 1000 );

このようなリクエストが失敗する可能性はまだあります。ヒープが十分に断片化されている場合、リクエストを処理するのに十分な大きさの単一の連続したブロックがない可能性があります。必要に応じて、段階的に割り当てることができます。行は必ずしもメモリ内で隣接しているとは限りませんが、必要なすべてのメモリを取得できる可能性が高くなります。

int ***ginormous = malloc( sizeof *ginormous * 1000 );
if ( ginormous )
{
  for ( size_t i = 0; i < 1000; i++ )
  {
    ginormous[i] = malloc( sizeof *ginormous[i] * 1000 );
    if ( ginormous[i] )
    {
      ginormous[i][j] = malloc ( sizeof *ginormous[i][j] * 1000 );
      if ( ginormous[i][j] )
      {
        // initialize ginormous[i][j][k]
      }
    }
  }
}

そして最後に、動的メモリを使用すると、リスト、ツリー、キューなどのデータを追加または削除するときに拡大および縮小できるコンテナを構築できます。追加すると拡大できる独自の実際の「文字列」データ型を構築することもできます。文字(C++のstring型と同様)。

21
John Bode

メモリーの最悪の場合の要件がわからない場合は、動的割り当てが必要です。その後、必要なメモリを静的に割り当てることはできません。必要なメモリ量がわからないためです。

最悪の要件を知っていても、動的メモリ割り当てを使用することが望ましい場合があります。これにより、システムメモリを複数のプロセスでより効率的に使用できます。すべてのプロセスは、ワーストケースのメモリ要件を静的にコミットできますが、これにより、システム上に存在できる実行可能なプロセスの数に上限が設けられます。すべてのプロセスが最悪のケースを同時に使用することが決してない場合、システムメモリは常に十分に活用されずに実行され、リソースの浪費になります。

副次的な質問については、Cでのmalloc()の呼び出しの結果をキャストしないでください。これにより、不足している宣言のバグを隠すことができます(暗黙の宣言はC.99より前に許可されていました)。未定義の動作。常にキャストなしでmalloc()の結果を取得することをお勧めします。 malloc()void *を返すように宣言されており、Cではvoid *と別のポインター型の間の変換が常に許可されます(constなどのモジュロ型修飾子)。

7
jxh

補足として、ptr_one = (int *)malloc(sizeof(int))int *ptr_one = malloc(sizeof(int))の違いは何ですか?

this を参照してください。

まず、動的なメモリ割り当てはCプログラミングで非常に重要なトピックであるため、これはとんでもない質問である可能性が高いことを知っています。しかし、これにより何ができるのか、なぜそれが必要なのかについて、私はうまく説明できませんでした。

メモリプール(またはより一般的にはヒープ)は、スタックと比較して非常に大きいです。スタック上でメモリプールを使用すると便利な理由として、次の2つの例を検討してください。

1。配列を定義し、それを複数のスタックフレーム間で永続化したい場合はどうなりますか?もちろん、それをグローバル変数として宣言すると、メモリのグローバルデータセクションに格納されますが、プログラムが大きくなるにつれて、乱雑になります。または、メモリプールに保存することもできます。

int *func( int k ) {
  assert( k >= 1 );

  int *ptr_block = malloc( sizeof( int ) * k );

  if ( ptr_block == NULL ) exit( EXIT_FAILURE );

  for ( int i = 0; i < k; i++ ) {
    ptr_block[ i ] = i + 1;
  }

  return ptr_block; // Valid.
}

...ただし、スタックに配列を定義した場合、これは機能しません。理由は、スタックフレームがポップされると、すべてのメモリアドレスは別のスタックフレームで使用できる(したがって上書きされる)のに対し、メモリプールのメモリを使用すると、ユーザー(ユーザー、またはクライアント)がfreedまで存続するためです。 )。

2。動的な配列を実装して、任意の大きな数列の読み取りを処理する場合はどうでしょうか?スタック上の配列を定義することはできません。メモリプールを使用する必要があります。ポインタを構造体に渡すことは非常に一般的であり(構造体を明示的にコピーする必要がない限り強くお勧めします)、構造体自体(絶対に大きくなる可能性があるため)を渡さないことを思い出してください。動的配列のこの小さな実装を考えてみましょう:

struct dyn_array {
  int *arr;
  int len;
  int cap;
};

typedef struct dyn_array *DynArray;

void insert_item( int const item, DynArray dyn_arr ) {
  // Checks pre conditions.
  assert( dyn_arr != NULL );

  // Checks if the capacity is equal to the length. If so, double.
  if ( dyn_arr->cap == dyn_arr->len ) {
    dyn_arr->cap *= 2;

    DynArray new_dyn_arr = malloc( sizeof( int ) * dyn_arr->cap ); // [oo]

    // ... copy, switch pointers and free...
  }

  // ... insert, increase length, etc.
}

... [oo]行で、これがスタックで定義されている場合、このスタックフレームがポップされると、配列のすべてのメモリアドレスが割り当てられなくなることに注意してください。つまり、別のスタックフレーム(おそらく次のスタックフレーム)がそれらのメモリアドレス(またはその一部)を使用します。

備考:コードのスニペットから、ptr_blockはスタックに格納されます。したがって、&ptr_blockはスタックアドレスですが、 ptr_blockの値は、メモリプールのどこかです。

2
Jacob Pollack