2

C(およびDebian GNU /LinuxではGCC4.3.3)のメモリ管理について質問があります。

K&RによるCプログラミング言語の本(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にすべきではありませんか?

呼び出しの順序をfreeに変更しようとすると、別の奇妙なことが起こります。例えば:

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+4 = 8
*x = 142106624

xポインターが実際に「解放」されているのに、pポインターが「異なって」解放されているのはなぜですか?わかりました。メモリを解放した後、ポインタをNULLを指すようにする必要があることはわかっていますが、興味があったのは:P

4

7 に答える 7

13

これは未定義の動作であるため、free奇妙なことが起こる可能性がある(そして起こる可能性がある)ため、dポインターを尊重することはエラーです。

free()ポインタの値を変更しないため、プロセスアドレス空間のヒープをポイントし続けます-そのため、segfaultは取得されませんが、指定されておらず、理論的には、一部のプラットフォームでは、freeingの直後にポインタを逆参照します。

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

一部のOS(HP-UX、他のOSもある場合があります)では、セグメンテーション違反を防ぐために(したがって、問題を隠すために)、NULLポインターを逆参照することが許可されていることに注意してください。この背後にある完全な話はわかりませんが、診断がはるかに難しくなるため、かなり愚かだと思います。

于 2009-07-03T09:59:38.327 に答える
10

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

特に、あなたが使用した後、あなたはあなたに与えfree()られたメモリブロックを解放していmalloc()ます。もうあなたのものではありません。もはや使用されることは想定されていないため、glibc内のメモリマネージャは、メモリブロックの一部を独自のメモリ構造として使用するなど、メモリブロックで自由に実行できます(これが、コンテンツの変更を確認している理由です。簿記情報、おそらく他のブロックまたはある種のカウンターへのポインターで上書きされています)。

起こりうることは他にもあります。特に、割り当てのサイズが十分に大きい場合、glibcはカーネルにそれ用の個別のメモリブロックを要求し(mmap()または同様の呼び出しを使用して)、の間にカーネルに解放しますfree()。その場合、プログラムはクラッシュします。これは、理論的には、割り当てが少ない場合でも一部の状況で発生する可能性があります(glibcはヒープを拡大/縮小できます)。

于 2009-07-03T12:47:11.810 に答える
9

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

形や形など、決して依存してはならない未定義の動作で遊んでいるので、特定の実装がそれをどのように正確に処理するかを知ることはどのようなメリットがありますか?

gccは、バージョン間、アーキテクチャ間、または月の位置と明るさに応じて、いつでもその処理を自由に変更できるため、現在の処理方法を知ることには何の役にも立ちません。少なくとも、gccを使用する開発者にとってはそうではありません。

于 2009-07-03T10:02:46.400 に答える
4

*(p + 4)は8、* xは0です。なぜこれが発生するのですか?*(p + 4)が8の場合、x配列の最初の要素は6なので、* xを6にすべきではありませんか?

これについて考えられる説明の1つは、printf( "...%i ..." ...)が内部でmallocを使用して、文字列補間用の一時バッファーを割り当てる可能性があることです。これにより、最初の出力後に両方の配列の内容が上書きされます。

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

于 2009-07-03T12:43:35.093 に答える
3

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

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

ちなみに、私のマシンでは違います。両方のポインタの値が変わります。

于 2009-07-03T10:10:28.097 に答える
2

あなたが見ている振る舞いは一貫しているように見えますが、そうであるとは限りません。予期しない状況により、この動作が変化する可能性があります(これは完全に実装に依存しているという事実は言うまでもありません)。

具体的には、この例では、配列をfree()し、配列にアクセスしたときに古いコンテンツを取得します。free()の後に追加のmalloc()呼び出しがある場合、古いコンテンツが失われる可能性があります。

于 2009-07-03T10:00:09.403 に答える
2

メモリがfreedであっても、必ずしも他の目的で再利用されるとは限りません。プロセスメモリへの古いポインタは(未割り当てのメモリへの)有効なポインタであるため、セグメンテーション違反も発生しません。

于 2009-07-03T10:01:57.510 に答える