1

クラスメートの 1 人がコードを送ってきて、何が問題なのか尋ねました。それは次のようなものでした:

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

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            realloc(d_array,(size + 1) * sizeof(int));
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

ユーザーから数字を取得し、素数の数字を保持して、最後にそれらを出力することになっています。私のコンピューターの出力は次のようなものです。

Enter a number:  3
Is there another number? y/n y
Enter a number:  5
Is there another number? y/n y
Enter a number:  8
Is there another number? y/n y
Enter a number:  7
Is there another number? y/n y
Enter a number:  2
Is there another number? y/n n
4072680
5
7
2

コードには他にもありましたが、最大の問題は明らかに realloc() の戻り値を割り当てていないことです。しかし、奇妙なことは、私の質問ですが、なぜこのコードは最初の素数が正しくなく、他の素数が正しく表示されるのでしょうか? 動的配列のアドレスは変更される可能性がありますが、最初のものではなく、2 番目のものと残りの部分が正しいのはなぜですか?

編集:わかりました、私がこれを尋ねた理由は、このコードでの realloc() の動作を理解しようとすることでした。適切なリソースがあれば共有してください。メモリを再割り当てするとき (古いメモリを解放するとき)、realloc() は古いメモリ位置の内容を変更しますか?

4

7 に答える 7

5

常にこれを行います:

void* new_ptr = realloc(ptr, new_size);
if(!new_ptr) error("Out of memory");
ptr = new_ptr;

その理由は、realloc()すでに割り当てられているブロック内で要求されたサイズに収まらない可能性があるためです。別のメモリ ブロックに移動する必要がある場合は、以前に割り当てられたメモリからデータをコピーし、古いメモリ ブロックを解放し、新しいものを返します。

また、realloc() が NULL を返す場合は、失敗したことを意味します。その場合、ptr が指すメモリをある時点で解放する必要があります。そうしないと、メモリ リークが発生します。つまり、次のことは絶対に行わないでください。

ptr = realloc(ptr, new_size);
于 2011-01-05T13:11:56.070 に答える
2

未定義の動作を呼び出している場合、オペレーティング システムとコンパイラの内部を見ずに結果を説明する方法はありません。

それは私が知っている非常に満足のいく答えではありません。しかし、その背後にあるロジックを説明できれば、実際には未定義の動作とは呼ばれません!

于 2011-01-05T13:28:38.297 に答える
1

未定義の動作から再現可能な (正しくない場合でも) 出力が得られる理由に最も興味があるように思われるので、ここに、あなたが見ている結果になる可能性のある 1 つのシナリオを示します。未定義の動作の背後にある根本的なメカニズムを理解することは価値があると思います。ただし、これは仮説であり、見ているものが実際に見える理由ではない可能性があることに注意してください。未定義の動作の場合、表示される結果はコンパイルごと、または実行ごとに変わる可能性があります (未定義の動作をしたプログラムで変数の名前を変更するだけでプログラムの出力が変わるという奇妙なケースについては、Unexpectedを参照してください)。 MSVC 対 TCC を使用した Bubblesort プログラムからの出力)。

  1. d_array/ループに入るmalloc(size * sizeof(int))前にへの呼び出しで初期化されます。この時点以降、ポインターは変更されません (後で説明するように、割り当てられたメモリを指していない可能性があります)。dowhiled_array

  2. 値が初めて格納されたときにrealloc()が呼び出されますが、ライブラリは最初に指定されたブロックを変更する必要がないことを検出し、渡された値を返します。So3はこのブロックの先頭に格納されます。d_arrayからの戻り値を無視したため、プログラムは がまだ有効かどうかを判断できないため、これはまだバグであることに注意してくださいrealloc()

  3. 5入力されると、別の呼び出しrealloc()が行われます。今度は、ライブラリは別のブロックを割り当てる必要があると判断します。そうします(何d_arrayを指すかの内容をコピーした後)。この再割り当ての一部は、ブロックd_arrayが解放されることを指しており、ライブラリのブックキーピング3は、このブロックにあった を で上書きし4072680ます。おそらく、ライブラリが気にかけている何かへのポインタです-誰が知っていますか. 主なことは、ブロックが再びライブラリに属し、(あなたではなく) ブロックでやりたいことができるようになったことです。

  4. Now5が書き込まれます (ブロックが解放されているd_array + 1ため、これは有効な操作ではありません)。d_arrayそうd_array[0] == 4072680そしてd_array[1] == 5

  5. この時点から、保存されたすべての値d_arrayは解放されたブロックに入りますが、何らかの理由でライブラリは発生しているヒープの破損に気付くことはありません。運だけです (バグを見つけたい場合は不運です)。realloc()実際に再割り当てされた可能性のあるブロックには何も書き込まれません。

注 - 私が言ったように、これはすべて動作の 1 つの可能な説明です。実際の詳細は異なる場合がありますが、実際には問題ではありません。解放されたメモリ割り当てにアクセスすると (読み取りまたは書き込みに関係なく)、すべての賭けはオフになります。未定義の動作の最終的なルールは、何でもありということです。

于 2011-01-05T18:57:52.873 に答える
1

これにより、何が起こっているかが少しわかります。

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

void dbg_print_array(unsigned sz, const int * array, const char * label) {
     fprintf(stderr, "{%s:\t%p:\t", label, array);
     while (sz--) {
         fprintf(stderr, " %x ", *array++);
     }
     fprintf(stderr, "}\n");
}

int main()
{
    int *d_array, number, divisor_count, i, size = 1;
    char answer;

    d_array = (int*) malloc(size * sizeof(int));
    dbg_print_array(size, d_array, "Initial");

    do
    {
        printf("\nEnter a number:  ");
        scanf("%d", &number);
        divisor_count = 0;
        for(i = 2; i < number; i++)
            if (number % i == 0) divisor_count++;
        if(divisor_count == 0)
        {
            int * p;
            dbg_print_array(d_array, size, "pre-realloc");
            p = realloc(d_array,(size + 1) * sizeof(int));
            dbg_print_array(d_array, size+1, "post-realloc (d_array)");
            dbg_print_array(p, size+1, "post-realloc (p)");
            d_array[size - 1] = number;
            size++;
        }
        printf("\nIs there another number? y/n ");
        getchar();
        answer = getchar();
    } while (answer == 'y');

    for(i = 0; i < size - 1; i++)
        printf("\n%d", d_array[i]);

    return 0;
} 

このデータがすぐに上書きされる理由については、判断が困難です。異なるヒープ割り当ての実装は、これに対して非常に異なる動作をする可能性があります。再割り当ては小さなステップで行われるため (配列は毎回 1 ずつ大きくなります)、realloc が頻繁に呼び出されます。

あなたと私は通常、未割り当てのヒープ領域を未使用と考えるかもしれませんが、ヒープの割り当てと解放機能は、物事に追いつくためにそこにいくつかのデータを保存します。realloc を呼び出すたびにこのデータが読み取られ、提供されたプログラムが realloc がおそらくヒープ割り当てルーチンによって所有されていると想定しているデータに書き込みを行うため、このプログラムが上書きしたものを読み取っている可能性があります (毎回最初に割り当てられたスペースの最後を書き込みます)。ループを介して)。この破損したデータreallocを読み取った後、読み取った内容に基づいて決定を下す可能性があり、その結果、誰が何を知っているかがわかります。プログラムのこの時点では、すべての動作を未定義と見なす必要があります。これは、通常の操作で行われた基本的な仮定がもはや有効ではないためです。

編集

上記のコードの出力を調べることで、渡されたものとは異なるポインターを実際にいつ返すかを判断できるはずreallocです(私の推測では、あなたの例では、mallocがおそらく切り上げられるため、最後に読み込まれた整数用のスペースを作ることです最初の割り当てでは 16 バイトまで -おそらく無効なポインターが渡されなかったため、そうではなかったからです) reallocabort

隣接するpost-reallocrealloc print ステートメントは、渡されたものと同じポインターを返さなかった場合、異なるアドレス (最初に出力される番号) を持ちます。

于 2011-01-05T23:13:31.790 に答える
1

問題は、reallocによって指されたメモリd_arrayが空きと見なされた後、コードが実際に空きメモリに書き込むことです。その間、メモリは別のものに割り当てられているようです(scanf?によって使用される)ため、その先頭が上書きされます。もちろん、これは完全に定義されていません。解放されたメモリのどの部分も、いつでも上書きされる可能性があります。

于 2011-01-05T13:10:53.997 に答える
0

私はあなたがこのようにreallocを使わなければならないと思います:

d_array = realloc(d_array、(size + 1)* sizeof(int));

だけでなく:

realloc(d_array、(size + 1)* sizeof(int));

私が見るもう1つの問題は、最初はsize = 1であるため、コードを初めて実行するときは次のようになります。

realloc(d_array、(size + 1)* sizeof(int));

size + 1 = 2(2つの整数にメモリを割り当てますが、必要なのは1つだけです。)ソリューションは、サイズを0から開始することができます。

于 2011-01-05T13:38:27.827 に答える
0

それを試してください:

d_array = realloc(d_array,(size + 1) * sizeof(int));

それをして、私のコンピューターでうまくいきました。

gcc を使用するgcc -Wall -oと、探していた警告が表示されます。

realloc() を実行しますが、割り当てられた新しいメモリを使用しません。簡単に言えば。それはまだ古いものを指しています(realloc()同じブロックを使用したか、別の場所に移動したかによって異なります)。「幸運」で同じブロックが使用された場合は、そのまま書き込みを続行します。そうでない場合は、古い場所に書き込みを行って、本来あるべきではない場所に書き込むことになります (内部realloc()的に free() が古いブロックを使用するため)。realloc()ポインタ変数を変更します, スペースを再割り当てするだけです. 返された結果でメモリアドレスを変更する必要がありますrealloc(). そしてコメントが言ったように. 成功したかどうかを確認する必要がありますrealloc(). 割り当てられた新しいスペースには古いバッファのデータが含まれています (限り割り当てられた新しいメモリが古いメモリよりも大きいためです)。

于 2011-01-05T13:08:09.143 に答える