4

私は C を書き始めてほんの数週間しか経っていませんmalloc()。しかし最近、私のプログラムは、私が期待していた真/偽の値ではなく、幸せそうな顔の文字列を返しました。

次のような構造体を作成すると:

typedef struct Cell {
  struct Cell* subcells;
} 

そして後でこのように初期化します

Cell makeCell(int dim) {
  Cell newCell;

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);
  }

  return newCell; //ha ha ha, this is here in my program don't worry!
}

どこかのメモリに保存されている幸せな顔にアクセスすることになるのでしょうか、それとも既存のセルを上書きすることになるのでしょうか? 私の質問は、適切な量のメモリを実際に malloc() していない場合、C はどのようにメモリを割り当てるのでしょうか? デフォルトは何ですか?

4

8 に答える 8

26

簡単な答え:あなたには割り当てられていません。

少し長い答え:subcellsポインターは初期化されておらず、どこかを指している可能性があります。これはバグであり、絶対に発生させてはなりませ

さらに長い答え:自動変数はスタックに割り当てられ、グローバル変数はコンパイラによって割り当てられ、多くの場合、特別なセグメントを占有するか、ヒープにある場合があります。グローバル変数はデフォルトでゼロに初期化されます。自動変数にはデフォルト値がなく (メモリにある値を取得するだけです)、プログラマーはそれらが適切な開始値であることを確認する責任があります (多くのコンパイラーは、忘れたときに手がかりを見つけようとします)。

関数内のnewCell変数は自動であり、初期化されていません。そのプロントを修正する必要があります。newCell.subcellsすぐに意味のある値を指定するかNULL、スペースを割り当てるまでポイントしてください。そうすれば、メモリを割り当てる前に逆参照しようとすると、セグメンテーション違反がスローされます。

さらに悪いことに、by 値を返していますが、配列を埋めようとするとCell、それを a に割り当てます。ヒープに割り当てられたオブジェクトへのポインターを返すか、ローカルに割り当てられたオブジェクトに値を割り当てます。Cell *subcells

これの通常の慣用句は、次のような形式になります。

Cell* makeCell(dim){
  Cell *newCell = malloc(sizeof(Cell));
  // error checking here
  newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
  // more error checking
  for (int i=0; i<dim; ++i){
    newCell->subCells[i] = makeCell(dim-1);
    // what error checking do you need here? 
    // depends on your other error checking...
  }
  return newCell;
}

私はあなたに解決すべきいくつかの問題を残しました..

そして、最終的に割り当てを解除する必要があるメモリのすべてのビットを追跡する必要があることに注意してください...

于 2009-03-02T21:42:33.447 に答える
17

ポインターのデフォルト値はありません。ポインターは、現在保存されているものを指します。初期化していないので、行

newCell.subcells[i] = ...

メモリの不確実な部分に効果的にアクセスします。subcells[i] は次と同等であることを思い出してください

*(newCell.subcells + i)

左側にガベージが含まれている場合i、ガベージ値に追加して、その不明な場所でメモリにアクセスすることになります。あなたが正しく言ったように、有効なメモリ領域を指すようにポインターを初期化する必要があります。

newCell.subcells = malloc(bytecount)

どの行の後、そのバイト数にアクセスできますか。他のメモリ ソースに関しては、さまざまな種類のストレージがあり、それぞれに用途があります。得られる種類は、所有しているオブジェクトの種類と、コンパイラに使用するように指示するストレージ クラスによって異なります。

  • malloc型のないオブジェクトへのポインターを返します。メモリのその領域を指すポインターを作成すると、オブジェクトの型は、ポイントされたオブジェクト型の型になります。メモリはどの値にも初期化されず、通常はアクセスが遅くなります。得られたオブジェクトを と呼びallocated objectsます。
  • オブジェクトをグローバルに配置できます。それらのメモリはゼロに初期化されます。ポイントの場合は NULL ポインターを取得し、フロートの場合は適切なゼロも取得します。適切な初期値に依存できます。
  • ローカル変数があり、staticストレージ クラス指定子を使用している場合、グローバル オブジェクトと同じ初期値ルールが適用されます。メモリは通常、グローバル オブジェクトと同じ方法で割り当てられますが、それは決して必要ではありません。
  • ストレージ クラス指定子のない、または を使用したローカル変数がある場合auto、変数はスタックに割り当てられます (C でそのように定義されていなくても、これはもちろんコンパイラが実際に行うことです)。そのアドレスを取得できます。その場合、コンパイラーはもちろんレジスターに入れるなどの最適化を省略しなければなりません。
  • ストレージ クラス指定子registerで使用されるローカル変数は、特別なストレージを持つものとしてマークされます。その結果、そのアドレスを取得できなくなります。register最近のコンパイラでは、洗練されたオプティマイザにより、通常はもう使用する必要はありません。ただし、あなたが本当に専門家であれば、それを使用するとある程度のパフォーマンスが得られるかもしれません.

オブジェクトには、さまざまな初期化ルールを示すために使用できる保存期間が関連付けられています (正式には、少なくともオブジェクトが存続する期間のみを定義します)。autoおよび で宣言されたオブジェクトは、register自動保存期間を持ち、初期化されません。何らかの値を含める場合は、明示的に初期化する必要があります。そうしないと、有効期間が始まる前にコンパイラがスタックに残したものがすべて含まれます。によって割り当てられたオブジェクトmalloc(またはそのファミリの別の関数などcalloc) には、ストレージ期間が割り当てられています。ストレージも初期化されません。例外は、使用する場合です。callocこの場合、メモリはゼロに初期化されます (「実際の」ゼロ。つまり、NULL ポインター表現に関係なく、すべてのバイトが 0x00 になります)。staticおよびグローバル変数で宣言されたオブジェクトには、静的ストレージ期間があります。それらのストレージ、それぞれのタイプに適したゼロに初期化されます。オブジェクトに型があってはならないことに注意してください。ただし、型のないオブジェクトを取得する唯一の方法は、割り当てられたストレージを使用することです。(C のオブジェクトは「ストレージの領域」です)。

それで、何が何ですか?これが固定コードです。メモリのブロックを割り当てたら、割り当てたアイテムの数を取り戻すことはできないため、そのカウントを常にどこかに保存することをお勧めします。dimカウントを格納する構造体に変数を導入しました。

Cell makeCell(int dim) {
  /* automatic storage duration => need to init manually */
  Cell newCell;

  /* note that in case dim is zero, we can either get NULL or a 
   * unique non-null value back from malloc. This depends on the
   * implementation. */
  newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
  newCell.dim = dim;

  /* the following can be used as a check for an out-of-memory 
   * situation:
   * if(newCell.subcells == NULL && dim > 0) ... */
  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim - 1);
  }

  return newCell;
}

これで、dim=2 の場合は次のようになります。

Cell { 
  subcells => { 
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    },
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    }
  },
  dim = 2
}

C では、関数の戻り値がオブジェクトである必要はないことに注意してください。ストレージが存在する必要はまったくありません。したがって、変更することはできません。たとえば、次のことはできません。

makeCells(0).dim++

割り当てられたメモリを再度解放する「解放機能」が必要になります。割り当てられたオブジェクトのストレージは自動的に解放されないためです。ツリー内のすべてのポインターfreeに対してそのメモリを解放するために呼び出す必要があります。subcellsあなたがそれを書き上げるための練習として残されています:)

于 2009-03-02T21:42:10.327 に答える
4

ヒープに割り当てられていないもの (mallocおよび同様の呼び出しを介して) は、代わりにスタックに割り当てられます。そのため、特定の関数で作成されたものはmalloc、関数が終了すると破棄されます。これには、返されたオブジェクトが含まれます。関数呼び出しの後にスタックが巻き戻されると、返されたオブジェクトは、呼び出し元関数によってスタック上に確保されたスペースにコピーされます。

警告:他のオブジェクトへのポインターを持つオブジェクトを返したい場合は、指しているオブジェクトがヒープ上に作成されていることを確認してください。それが作成される関数。

于 2009-03-02T21:41:50.203 に答える
3

私の質問は、適切な量のメモリを実際に malloc() していない場合、C はどのようにメモリを割り当てるのでしょうか? デフォルトは何ですか?

メモリを割り当てない。スタック上または動的に明示的に作成する必要があります。

あなたの例では、 subcellsはバグである未定義の場所を指しています。関数は、ある時点で Cell 構造体へのポインターを返す必要があります。

于 2009-03-02T21:43:30.590 に答える
0

私はここのコンピューターのふりをして、このコードを読んでいます...

typedef struct Cell {
  struct Cell* subcells;
}

これは私に教えてくれます:

  • Cellという構造体タイプがあります
  • サブセルと呼ばれるポインタが含まれています
  • ポインタはstructCell型のものである必要があります

ポインタが1つのセルに移動するのか、セルの配列に移動するのかはわかりません。新しいセルが作成されると、そのポインタの値は、値が割り当てられるまで未定義になります。ポインタを定義する前にポインタを使用するのは悪いニュースです。

Cell makeCell(int dim) {
  Cell newCell;

未定義のサブセルポインタを持つ新しいセル構造体。これは、Cell構造体のサイズであるnewCellと呼ばれるメモリの小さなチャンクを予約するだけです。そのメモリにあった値は変更されません-それらは何でもかまいません。

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);

newCell.subcells [i]を取得するために、サブセルからiだけオフセットする計算が行われ、それが逆参照されます。具体的には、これは値がそのメモリアドレスから取得されることを意味します。たとえば、i == 0 ...とすると、サブセルポインタ自体を逆参照します(オフセットなし)。サブセルは未定義なので、何でもかまいません。文字通り何でも!したがって、これはメモリ内の完全にランダムな場所からの値を要求します。結果が何であるかを保証するものではありません。何かを印刷したり、クラッシュしたりする可能性があります。それは絶対に行われるべきではありません。

  }

  return newCell;
}

ポインタを操作するときはいつでも、間接参照する前に、ポインタが値に設定されていることを確認することが重要です。コンパイラーに警告を出すように勧めてください。最近のコンパイラーの多くは、この種のことを捕らえることができます。また、ポインターに0xdeadbeefのようなかわいいデフォルト値を指定して(うん!これは16進数で、単語でもあるので、面白く見えます)、目立つようにすることもできます。(printfの%pオプションは、デバッグの大まかな形式としてポインターを表示するのに役立ちます。デバッガープログラムもポインターを非常にうまく表示できます。)

于 2009-03-03T01:00:20.880 に答える
0

どこかのメモリに保存されている幸せな顔にアクセスすることになるのでしょうか、それとも既存のセルを上書きすることになるのでしょうか?

幸せそうな顔をしたあなたは幸運です。不運な日には、システムが完全に消去された可能性があります ;)

私の質問は、適切な量のメモリを実際に malloc() していない場合、C はどのようにメモリを割り当てるのでしょうか?

そうではありません。ただし、Cell newCell を定義すると、subCells ポインタがガベージ値に初期化されます。これは、0 (この場合、クラッシュが発生します) または実際のメモリ アドレスのように見せるのに十分な大きさの整数です。そのような場合、コンパイラは、そこにある値を喜んで取得し、それをユーザーに返します。

デフォルトは何ですか?

これは、変数を初期化しない場合の動作です。そして、あなたのmakeCell機能は少し未開発に見えます。

于 2009-03-02T21:45:34.047 に答える
0

実際には、データ、スタック、ヒープの 3 つのセクションに物を割り当てることができます。

あなたが言及した場合、それはスタックに割り当てられます。スタックに何かを割り当てる際の問題は、関数の実行中のみ有効であることです。関数が戻ると、そのメモリは回収されます。したがって、スタックに割り当てられた何かへのポインターを返すと、そのポインターは無効になります。ただし、実際のオブジェクト (ポインターではない) を返す場合は、呼び出し元の関数が使用するオブジェクトのコピーが自動的に作成されます。

グローバル変数として宣言した場合 (ヘッダー ファイル内または関数の外など)、メモリのデータ セクションに割り当てられます。このセクションのメモリは、プログラムの開始時に自動的に割り当てられ、終了時に自動的に解放されます。

malloc() を使用してヒープに何かを割り当てた場合、解放される時点で free() を呼び出すまで、そのメモリは使用したい限り有効です。これにより、必要に応じてメモリの割り当てと割り当て解除を柔軟に行うことができます (すべてが事前に割り当てられ、プログラムの終了時にのみ解放されるグローバルを使用するのとは対照的です)。

于 2009-03-02T21:47:05.823 に答える
0

ローカル変数はスタックに「割り当て」られます。スタックは、これらのローカル変数を保持するために事前に割り当てられたメモリ量です。関数が終了すると、変数は有効ではなくなり、次に来るものによって上書きされます。

あなたの場合、結果を返さないため、コードは何もしていません。また、スコープが終了すると、スタック上のオブジェクトへのポインターも無効になるため、正確なケースでは (リンクされたリストを実行しているように見えます)、malloc() を使用する必要があります。

于 2009-03-02T21:43:26.440 に答える