2009-07-03 17 views
2

私はC(とDebian GNU/LinuxのGCC 4.3.3)のメモリ管理について質問しました。ポインタを解放した後に逆参照すると、結果が変わるのはなぜですか?

Cのプログラミング言語の書籍K & Rによると(7.8.5節)、ポインタを解放して逆参照するとエラーになります。しかし、私はいくつか疑問を抱きました。私は、以下に貼り付けたソースのように、コンパイラ(?)がうまく定義された原則に従って動作するように見えることがあることに気付きました。

私はこのような些細なプログラムをしました、それは、動的に割り当てられた配列を返す方法を示しています。

#include <stdio.h> 
#include <stdlib.h> 


int * ret_array(int n) 
{ 
    int * arr = (int *) malloc(10 * sizeof(int)); 
    int i; 
    for (i = 0; i < n; i++) 
    { 
     arr[i] = i*2; 
    } 
    printf("Address pointer in ret_array: %p\n", (void *) arr); 
    return arr; 
} 

int * ret_oth_array(int n) 
{ 
    int * arr = (int *) malloc(10 * sizeof(int)); 
    int i; 
    for (i = 0; i < n; i++) 
    { 
     arr[i] = i+n; 
    } 
    printf("Address pointer in ret_oth_array: %p\n", (void *) arr); 
    return arr; 
} 

int main(void) 
{ 
    int *p = NULL; 
    int *x = NULL; 
    p = ret_array(5); 
    x = ret_oth_array(6); 

    printf("Address contained in p: %p\nValue of *p: %d\n", (void *) p, *p); 

    free(x); 
    free(p); 
    printf("Memory freed.\n"); 
    printf("*(p+4) = %d\n", *(p+4)); 
    printf("*x = %d\n", *x); 

    return 0; 
} 

私はいくつかの引数でそれをコンパイルしようとした場合:-ansi -Wall -pedantic-errors、それはエラーや警告を発生させていません。だけでなく、それはまた正常に動作します。

Address pointer in ret_array: 0x8269008 
Address pointer in ret_oth_array: 0x8269038 
Address contained in p: 0x8269008 
Value of *p: 0 
Memory freed. 
*p+4 = 8 
*x = 0 

*(p + 4)が8であり、* X 0 なぜこれが起こるんですか? *(p + 4)が8の場合、x配列の最初の要素は6なので* xは6であってはなりませんか?

私は無料で通話の順序を変更しようとすると、別の奇妙なことが起こります。 例:実際には

int main(int argc, char * argv[]) 
{ 
/* ... code ... */ 

    free(p); 
    free(x); 

    printf("Memory freed.\n"); 
    printf("*(p+4) = %d\n", *(p+4)); 
    printf("*x = %d\n", *x); 

    return 0; 
} 

この場合には(私のマシン上の)出力は次のようになります。Pポインタがある間、

*p+4 = 8 
*x = 142106624 

はなぜXポインタは本当に、「解放」されません私は「異なって」解放されましたか? [OK]を、私はメモリを解放した後、私はポインタがNULLを指すようにすべきであることを知っているが、私はちょうど興味があった:P

+0

複数回実行してから結果を比較してみてください – Vaibhav

+0

私は少なくとも10回実行しましたが、いつも同じ結果が得られます – Markon

+1

同じマシン上のシングルスレッドプログラムでは、これらはしばしば再現性があります。なぜ彼らが「機械上の仕掛け」につながるのか?バグの種類。しかし、その前/後にいくつかのコードを追加し、別のスレッドを追加して、別のコンパイラ設定を使用すると、別の結果が得られます – Niki

答えて

10

free()(およびmalloc())はgcc由来ではありません。それらはDebian上では通常glibcであるCライブラリから来ています。ですから、あなたが見ているのはgccのものではなく、glibcの動作です(そして、別のCライブラリ、あるいは別のバージョンのCライブラリで変更されるでしょう)。

私は特にfree()を使用した後、メモリブロックを解放していますmalloc()はあなたに与えました。もはやあなたのものではありません。 glibc内のメモリマネージャは、メモリブロックで必要なものを自由に使用できます。メモリブロックの一部を独自のメモリ構造として使用することもできます(おそらく、その内容が変化していることがわかります。簿記情報、他のブロックへのポインタ、またはある種のカウンタで上書きされています)。

他にも起こることがあります。特に、割り当てのサイズが十分に大きかった場合、glibcはカーネルに別のメモリブロック(mmap()または同様の呼び出し)を要求し、free()の間にカーネルに戻します。その場合、プログラムがクラッシュします。理論的には、小さな割り当てでも(glibcはヒープを拡張/縮小できます)、いくつかの回覧でも起こります。

+0

詳細な回答ありがとう:) glibcのマニュアルを読んでください。 – Markon

13

それは未定義の動作ですので、奇妙なことが(5月としてはfree Dポインタを欽慕するとエラーになりますそして起こります)。

free()ポインタがプロセスアドレス空間のヒープを指し示すようにポインタの値を変更しないため、segfaultが得られないのですが、指定されていません。理論上、いくつかのプラットフォームでfree ingの直後にポインタを参照解除しようとすると、segfaultが発生します。

これを防ぐには、freeの後にNULLにポインタを割り当てるのが良い習慣です。それで、予測可能な方法で失敗するでしょう - segfault。

一部のOS(HP-UXなど)では、segfaultを防ぐためにNULLポインタを参照解除することができます(したがって、問題を隠す)ことに注意してください。私は、このことの背後にあるすべての話を知らないが、診断するのがはるかに難しくなるので、むしろ馬鹿だと思う。

2

あなたが見ている動作は一貫しているようですが、そうであることは保証されていません。予期しない状況により、この動作が変更される可能性があります(これは完全に実装が依存しているという事実だけではありません)。

具体的には、この例では配列を解放()してから、その配列にアクセスすると古いコンテンツを取得します。 free()の後にmalloc()を追加すると、古い内容が失われる可能性があります。

+0

しかし、* pが解放された後も* pは同じ結果を持ちますが、* xは0またはその他のランダムな値ですか?この場合、私はxのメモリを再マップしないので、6でなければならないのですか? もしあなたが正しいのであれば、それはスタックとして機能するはずです。ヒープポインタ(存在する場合)が最初に減少よりも大きくなった場合、あなたの意味を理解すれば... – Markon

2

メモリがfree dであっても、必ずしも他の目的で再利用されるわけではありません。あなたのプロセスメモリへの古いポインタはまだ有効なポインタです(割り当てられていないメモリまで)ので、セグメンテーションフォルトも取得されません。

9

は、これはおそらくあなたが探している答えではありませんが、私はそれがとにかく試してみるよ:

あなたはどのような方法、形状または形態に依存することはありません未定義の動作でプレーしているので、ある実装がそれをどのように処理するかを知るためには何が良いのでしょうか?

gccはバージョン、アーキテクチャ間、または月の位置と明るさに応じて、いつでもそのハンドリングを変更することができますので、現在どのように処理しているかを知ることはできません。少なくともgccを使用する開発者にとってはそうではありません。

3

一度ダイナミックメモリ変数を解放すると、それはあなたのものではありません。メモリマネージャーは、あなたが指している場所のメモリで、これまでどおりに行うことができることは自由です。コンパイラは、解放されたメモリブロックについては、それが関数であり、言語によって定義されていないので、私が知る限り何もしません。言語によって定義されていても、コンパイラは基礎となるOS関数への呼び出しを挿入するだけです。

言いたいことは、言語によっては定義されていないため、OSをチェックしてメモリを解放した後にそのメモリを監視する必要があります。時には他のプログラムがメモリを要求することがあるため、動作がランダムかもしれません。

ところで、私のマシンでは、両方のポインタの値が変わります。

4

*(p + 4)は8であり、* xは0です。なぜこれが起こりますか? *(p + 4)が8の場合、x配列の最初の要素は6なので、* xは6であってはなりませんか?

これは、printf( "...%i ..." ...)が内部的にmallocを使用して、文字列の補間に一時バッファを割り当てるかもしれないという説明です。これは、最初の出力の後に両方の配列の内容を上書きします。

一般に、プログラムが解放された後にポインタの値に依存すると、私はそれをエラーと見なします。私はそれが解放された後にポインタの値を保持するならば、それがスコープから外れるか、NULLで上書きするのではなく、それが非常に悪いコードのにおいであると言うでしょう。非常に特殊な状況(特定のヒープマネージャを持つシングルスレッドコード)で動作しても。

関連する問題