2013-05-09 30 views
7

私は構造を定義した場合...構造体のメンバにアクセスできるのはなぜですか?

struct LinkNode 
{ 
    int node_val; 
    struct LinkNode *next_node; 
}; 

し、それへのポインタを作成...

struct LinkNode *mynode = malloc(sizeof(struct LinkNode)); 

...そして最終的には無料()それ...

free(mynode); 

...構造体の 'next_node'メンバーにはまだアクセスできます。

mynode->next_node 

私の質問はこれです:基礎となる力学のどの部分がメモリのこのブロックは構造体LinkNodeを表現することになっているという事実を追跡しますか?私はCの初心者ですが、LinkNodeへのポインタにfree()を使用した後、その構造体のメンバーにアクセスすることができなくなると思いました。私はある種の「もう利用できなくなった」という警告を期待していました。

基本的なプロセスの仕組みについてもっと知りたいと思います。

+0

無料通話でメモリが蒸発すると思いますか?それはまだそこにある(ない)か、それは別の意味を持つ可能性があります。 – wildplasser

+0

@wildplasserあなたはそこでsnarkyされていますか?私は言語の構文だけでなく、それがなぜ機能するのかを理解しようとしています。もちろん、メモリは蒸発しません。しかし、私が* pointer-> memberを書くと、その仕組みがどのように機能しているのか、そしてポインタがfree()の後でまだ動作している理由が分かりません。希望は意味をなさない。 – Ducain

+1

ポインタの値は同じです。 free()を呼び出した後、あなたはメモリを放棄しました:あなたはmalloc/freeに、もう使用したくないと言ったのです。それを電話番号と比較してください:私の電話の添付ファイルを終了した後、私の番号はもはや有効ではありません。しかし、まだダイヤルしようとすることができます。それは私が電話に答えるかもしれない。または騒音。または全く異なる誰か。番号(=アドレス)はまだ存在しますが、それを使用することはもはや有効ではありません。それは原子力発電所のコントロールを指すことができる... – wildplasser

答えて

6

コンパイルされたプログラムは、struct LinkedNodeまたはフィールドnext_node、またはそのようなものについて何も知らなくなりました。すべての名前は、コンパイルされたプログラムから完全になくなっています。コンパイルされたプログラムは、メモリアドレス、オフセット、インデックスなどの役割を果たすことができる数値の観点から動作します。単にいくつかの予約されたメモリ位置から4バイトの数値を読み取り、あなたのプログラムのソースコードでmynode->next_nodeを読んだとき、それはマシンコードにコンパイルされ、あなたの例で

は、(ソースコード内の変数mynodeとして知られています)それに4を加え(これはnext_nodeフィールドのオフセットである)、結果アドレス(これはmynode->next_node)で4バイトの値を読み込みます。このコードは、わかるように、整数値(アドレス、サイズ、オフセット)の観点から動作します。それはLinkedNodeまたはnext_nodeのような名前は気にしません。メモリが割り当てられているか解放されているかは気にしません。これらのアクセスのいずれかが合法であるかどうかは気にしません。

(I繰り返し上記の例で使用する定数4は、64ビットプラットフォームでは、それが)ほとんど(またはすべてのインスタンスに8によって置換されるであろう。32ビット・プラットフォームに特異的である。)

試みがあれば解放されたメモリを読み取らせると、これらのアクセスによってプログラムがクラッシュする可能性があります。そうでないかもしれない。それは純粋な運の問題です。言語に関する限り、その動作は未定義です。

+0

ロッキン '。ちょうど私が後にした情報の種類。あなたもありがとう。 – Ducain

4

ありません。これは古典的なケース未定義の動作です。

動作が定義されていない場合、何かが起こる可能性があります。それはうまくいくように見えるかもしれませんが、1年後にランダムにクラッシュするだけです。

+1

これは実際に質問された質問には答えません。具体的には構造体メンバの相対的位置付けは、たとえアクセス結果が未定義であっても、なぜ有効であるのでしょうか。 –

+0

@ChrisStratton:公共のロッカーからロックを外しても、物を残しておき、後で戻ってきて見ても、あなたのものはそこに残っている可能性があります。または、あなたのように見えるが、実際には同じような好みの他の人に属しているかもしれません。あなたが最後にそれを配置したときとまったく同じようにロッカーにあなたのものを見つけることが起こった場合、誰もスペースを使って何もしなかったということだけがそこにある唯一の "理由"です。 – supercat

+1

これは見落としている側面は、あなたの右から3番目のロッカーは、もはやあなたのものではなく、あなたが持っていたものの3番目のロッカーであるということです。また、コメントでは、その行動は実際に**必ずしも**未定ではないことが指摘されています。 –

2

下位のメモリのどの部分もそれを記録しません。プログラミング言語がメモリのチャンクに与えるセマンティクスです。たとえば、完全に異なるものにキャストして、同じメモリ領域にアクセスすることができます。しかし、ここでのキャッチは、これがエラーにつながる可能性が高いということです。特にタイプセーフティーはなくなります。あなたのケースでは、freeと呼んでいるからといって、基本的なメモリがまったく詰まっているわけではありません。オペレーティングシステムには、この領域を再び空き領域としてマークするフラグがあります。

このように考えると、freeは "最小限の"メモリ管理システムのようなものです。あなたの呼び出しがフラグを設定する以上のものを必要とするなら、それは不必要なオーバーヘッドを招くでしょう。また、メンバにアクセスすると、あなた(つまりあなたのオペレーティングシステム)は、このメモリ領域のフラグが "フリー"または "使用中"に設定されているかどうかをチェックできます。しかしそれはまた間接費です。

もちろん、そういうものをするのは意味がないわけではありません。セキュリティ上の問題を避けるために、.NETやJavaなどで行われます。しかし、これらのランタイムはCよりはるかに若く、最近ではもっと多くのリソースを持っています。

4

解放されたメモリがまだ別のものによって上書きされていないため、これは純粋な運がうまくいきます。一度メモリを解放すると、再度メモリを使用しないようにする必要があります。

2

コンパイラがCコードを実行可能なマシンコードに変換すると、タイプ情報を含む多くの情報が破棄されます。あなたが書く場合は:

int x = 42; 

生成されたコードは、単にコピーし、メモリの特定のチャンク(通常は4バイトであるかもしれないチャンク)に、特定のビットパターン。メモリのチャンクがタイプintのオブジェクトであることを機械コードを調べることによって知ることはできません。

同様に、書き込み時:

if (mynode->next_node == NULL) { /* ... */ } 

を生成されたコードは、メモリの別のポインタサイズのチャンクを逆参照することにより、メモリのポインタサイズのチャンクをフェッチし、ヌルポインタのシステムの表現に結果を比較します(典型的には全ビット・ゼロ)。生成されたコードは、next_nodeが構造体のメンバーであるという事実、または構造体がどのように割り当てられたか、またはそれがまだ存在するかどうかについては何も直接反映していません。

コンパイラはコンパイル時に多くのことをチェックできますが、実行時にチェックを実行するコードを生成するとは限りません。最初にエラーを起こさないようにすることは、プログラマとしての責任です。

この特定のケースでは、freeを呼び出した後、mynodeは不定値を持ちます。どのような有効なオブジェクトも指していませんが、実装がその知識で何かを行う必要はありません。 freeを呼び出すと、割り当てられたメモリが破棄されることはなく、ただちにmallocを呼び出すことで割り当て可能になります。

実装方法のようにチェックを行い、freeの後にポインタを間接参照すると実行時エラーが発生することがあります。しかし、そのようなチェックはC言語では必要ではなく、(a)コストが高くなり、プログラムの実行が遅くなり、(b)チェックですべてのエラーを捕まえることができないため、一般的に実装されません。

Cが定義されているため、プログラムがすべて正常に動作してもメモリ割り当てとポインタ操作が正しく機能します。コンパイル時に特定のエラーを検出すると、コンパイラはそのエラーを診断できます。たとえば、ポインタ値を整数オブジェクトに代入するには、少なくともコンパイル時の警告が必要です。しかし、freeポインタを逆参照するなどの他のエラーは、プログラムにの定義されていない動作を引き起こします。プログラマとして、最初にこれらのエラーを避けることは、あなた次第です。あなたが失敗した場合、あなたは自分でいる。

もちろん、役立つツールがあります。 Valgrindは1つです。賢明な最適化コンパイラは別のものです。 (最適化を有効にすると、コンパイラはコードの分析をさらに進め、多くの場合、エラーを診断することができます。)しかし、最終的にC言語はあなたの手を保持する言語ではありません。これは鋭いツールです。また、より多くの実行時検査を行うインタープリター言語など、より安全なツールを構築するために使用できるツールです。

+0

オプティマイザは、バグのあるコードを壊すという意味でバグを見つけるのにも役立ちます。最適化せずに動作するように見えるコードは、最適化してコンパイルすると明らかに壊れる可能性があります。 – Antimony

+0

@Antimony:はい、コードに未定義の動作がある場合、コンパイラは好きなコードを生成できます。最適化は、実際の動作を変更し、バグを明らかにする意思決定に影響を与える可能性があります。 –

+0

実際には、Cでは、ポインタを間接参照するときにnullのチェックは通常ありません。代わりに、それは盲目的に試みられますが、現代の多くのシステムではメモリ保護ユニットに障害が発生します。そして、いいえ、mynodeの値は** not ** indeterminateではありません。それはまだそれがそれを使用した場所を指しています、それは今そこにあるメモリが他の何かのために使用されているかもしれないかもしれません。 –

0

あなたは> next_node mynode-するNULLを割り当てる必要があります:

mynode-> next_node = NULL;

メモリを解放した後で、割り当てられたメモリをもう使用していないことを示します。

NULL値を割り当てることなく、以前に解放されたメモリ位置を指しています。

関連する問題