質問
私はほんの数週間だけ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はどのようにメモリを割り当てるのですか?デフォルトは何ですか?
解決
ポインタのデフォルト値はありません。ポインタは、現在保存されているものを指します。初期化していないので、行
newCell.subcells[i] = ...
メモリの不確実な部分に効果的にアクセスします。サブセル[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のオブジェクトは「ストレージの領域」です)。
では、何が何ですか?これが修正されたコードです。一度メモリのブロックを割り当てると、割り当てたアイテムの数を取り戻すことができなくなるため、常にそのカウントをどこかに保存するのが最善です。カウントを保存する構造体にvariale dim
を導入しました。
他のヒント
簡単な答え:割り当てられていません。
少し長めの回答: subcells
ポインターは初期化されておらず、どこでもを指している場合があります。これはバグであり、あなたは決してそれを許してはなりません。
さらに長い答え:自動変数はスタックに割り当てられ、グローバル変数はコンパイラによって割り当てられ、多くの場合、特別なセグメントを占有するか、ヒープ内に存在します。グローバル変数はデフォルトでゼロに初期化されます。自動変数にはデフォルト値がありません(メモリ内で検出された値を取得するだけです)。プログラマーは、適切な開始値を持っていることを確認する責任があります(多くのコンパイラーは、忘れると手掛かりを試みます)。
関数の newCell
変数は自動であり、初期化されません。そのプロントを修正する必要があります。 newCell.subcells
にすぐに意味のある値を指定するか、スペースを割り当てるまで NULL
を指定します。そうすれば、メモリを割り当てる前に間接参照しようとすると、セグメンテーション違反がスローされます。
さらに悪いことに、値で Cell
を返していますが、 subcells
配列を埋めようとすると Cell *
に割り当てられます。ヒープに割り当てられたオブジェクトへのポインターを返すか、ローカルに割り当てられたオブジェクトに値を割り当てます。
これの通常のイディオムは次のような形式になります
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;
}
ただし、いくつかの問題を解決するために残しました。.
そして、最終的に割り当てを解除する必要があるメモリのすべてのビットを追跡する必要があることに注意してください...
( malloc
および同様の呼び出しを介して)ヒープに割り当てられていないものはすべて、代わりにスタックに割り当てられます。そのため、特定の関数で malloc
を使用せずに作成されたものはすべて、関数の終了時に破棄されます。返されるオブジェクトが含まれます。関数呼び出し後にスタックが巻き戻されると、返されたオブジェクトは、呼び出し元関数によってスタック上に確保されたスペースにコピーされます。
警告:他のオブジェクトへのポインターを持つオブジェクトを返す場合は、指すオブジェクトがヒープ上に作成されていることを確認してください。ヒープも、それが作成された関数を存続させることを意図していない場合を除きます。
質問は、実際に適切な量のメモリをmalloc()しなかった場合、Cはどのようにメモリを割り当てますか?デフォルトは何ですか?
メモリを割り当てません。スタック上または動的に明示的に作成する必要があります。
あなたの例では、サブセルは undefined の場所を指しますが、これはバグです。関数は、ある時点でCell構造体へのポインターを返す必要があります。
メモリに保存されている幸せそうな顔にアクセスしたり、既存のセルに上書きしたりしますか?
幸運なことに、幸せそうな顔ができました。それらの不運な日には、システムをきれいに拭いたかもしれません;)
質問は、実際に適切な量のメモリをmalloc()しなかった場合、Cはどのようにメモリを割り当てますか?
そうではありません。ただし、Cell newCellを定義すると、subCellsポインターがガベージ値に初期化されます。これは0(この場合はクラッシュします)または実際のメモリアドレスのように見えるのに十分な整数です。コンパイラは、そのような場合、そこにある値を喜んでフェッチし、それをあなたに戻します。
デフォルトは何ですか?
これは、変数を初期化しない場合の 動作です。そして、あなたの makeCell
関数は少し未発達に見えます。
実際に物事を割り当てることができる3つのセクションがあります-データ、スタック、ヒープ。
言及した場合、スタックに割り当てられます。スタックに何かを割り当てる際の問題は、それが関数の持続時間の間のみ有効であることです。関数が戻ると、そのメモリは回収されます。そのため、スタックに割り当てられた何かへのポインターを返すと、そのポインターは無効になります。 (ポインタではなく)実際のオブジェクトを返す場合、呼び出し関数が使用するオブジェクトのコピーが自動的に作成されます。
グローバル変数として宣言した場合(たとえば、ヘッダーファイルまたは関数の外部)、メモリのデータセクションに割り当てられます。このセクションのメモリは、プログラムの開始時に自動的に割り当てられ、終了時に自動的に割り当て解除されます。
malloc()を使用してヒープに何かを割り当てた場合、そのメモリは使用したい限り有効です-free()を呼び出すまで解放されます。これにより、必要に応じてメモリの割り当てと割り当て解除を柔軟に行うことができます(すべてが事前に割り当てられ、プログラムの終了時にのみ解放されるグローバルを使用するのではなく)。
ローカル変数は「割り当て済み」ですスタック上。スタックは、これらのローカル変数を保持するために事前に割り当てられたメモリの量です。関数が終了すると変数は有効でなくなり、次に来るものによって上書きされます。
あなたの場合、結果を返さないため、コードは何もしていません。また、スコープの終了時にスタック上のオブジェクトへのポインターも無効になるため、正確な場合(リンクリストを使用しているようです)、malloc()を使用する必要があります。
このコードを読んで、私はここにいるコンピュータのふりをするつもりです...
typedef struct Cell {
struct Cell* subcells;
}
これは私に言う:
- Cellという構造型があります
- サブセルと呼ばれるポインターが含まれています
- ポインターはstruct Cell型のものでなければなりません
ポインターが1つのCellに行くのか、Cellの配列に行くのかはわかりません。新しいセルが作成されると、そのポインターの値は、値が割り当てられるまで定義されません。ポインターを定義する前に使用するのは悪いニュースです。
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オプションは、デバッグの粗い形式として、ポインターを表示するのに役立ちます。デバッガープログラムは、それらを非常によく表示することもできます。)