1

メモリを解放すると、そのメモリを指すポインターはどうなりますか? それらはすぐに無効になりますか?後で再び有効になった場合はどうなりますか?

確かに、ポインターが無効になり、再び「有効」になる通常のケースは、以前に使用されたメモリに別のオブジェクトが割り当てられることです。ポインタを使用してメモリにアクセスすると、それは明らかに未定義の動作です。ダングリング ポインター メモリの上書きレッスン 1 です。

しかし、同じ割り当てに対してメモリが再び有効になったらどうなるでしょうか。それを実現するための標準的な方法は 1 つだけですrealloc()malloc()'d メモリ ブロック内のどこかに offset へのポインタがある場合> 1、 を使用realloc()してブロックをオフセット未満に縮小すると、ポインタは明らかに無効になります。その後realloc()、ダングリング ポインターが指すオブジェクト型を少なくともカバーするようにブロックを再度拡張し、どちらの場合もrealloc()メモリ ブロックを移動しなかった場合、ダングリング ポインターは再び有効になりますか?

これは非常にまれなケースであるため、C または C++ 標準を解釈して理解する方法がよくわかりません。以下はそれを示すプログラムです。

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

int main(void)
{
    static const char s_message[] = "hello there";
    static const char s_kitty[] = "kitty";

    char *string = malloc(sizeof(s_message));
    if (!string)
    {
        fprintf(stderr, "malloc failed\n");
        return 1;
    }

    memcpy(string, s_message, sizeof(s_message));
    printf("%p %s\n", string, string);

    char *overwrite = string + 6;
    *overwrite = '\0';
    printf("%p %s\n", string, string);

    string[4] = '\0';
    char *new_string = realloc(string, 5);
    if (new_string != string)
    {
        fprintf(stderr, "realloc #1 failed or moved the string\n");
        free(new_string ? new_string : string);
        return 1;
    }
    string = new_string;
    printf("%p %s\n", string, string);

    new_string = realloc(string, 6 + sizeof(s_kitty));
    if (new_string != string)
    {
        fprintf(stderr, "realloc #2 failed or moved the string\n");
        free(new_string ? new_string : string);
        return 1;
    }

    // Is this defined behavior, even though at one point,
    // "overwrite" was a dangling pointer?
    memcpy(overwrite, s_kitty, sizeof(s_kitty));
    string[4] = s_message[4];
    printf("%p %s\n", string, string);
    free(string);
    return 0;
}
4

3 に答える 3

7

メモリを解放すると、そのメモリを指すポインターはどうなりますか? それらはすぐに無効になりますか?

はい、間違いなく。C標準のセクション6.2.4から:

オブジェクトの存続期間は、プログラム実行の中で、そのオブジェクト用にストレージが確保されることが保証されている部分です。オブジェクトは存在し、一定のアドレスを持ち、最後に保存された値を存続期間を通じて保持します。オブジェクトが有効期間外に参照された場合、動作は未定義です。ポインターの値は、それが指している (または直前の) オブジェクトがその存続期間の終わりに達すると、不確定になります。

そして、セクション 7.22.3.5 から:

realloc 関数は、ptr が指す古いオブジェクトの割り当てを解除し、size で指定されたサイズを持つ新しいオブジェクトへのポインターを返します。新しいオブジェクトの内容は、新しいサイズと古いサイズの小さい方まで、割り当て解除前の古いオブジェクトの内容と同じでなければなりません。古いオブジェクトのサイズを超える新しいオブジェクトのバイトの値は不定です。

古いオブジェクト新しいオブジェクトへの参照に注意してください...標準では、 realloc から返されるものは、以前に持っていたものとは異なるオブジェクトです。a を実行してから a を実行するのfreemalloc同じであり、2 つのオブジェクトが同じアドレスを持っているという保証はありません。新しいサイズが古いサイズよりも小さい場合でも、実際の実装ではそうではないことがよくあります。さまざまな空きリストからさまざまなサイズが抽出されます。

後で再び有効になった場合はどうなりますか?

そんな動物はいません。有効性は発生するイベントではなく、C 標準によって設定された抽象的な条件です。一部の実装ではポインターがたまたま機能する場合がありますが、ポインターが指すメモリを解放すると、すべての賭けが無効になります。

しかし、同じ割り当てに対してメモリが再び有効になったらどうなるでしょうか。それを実現するための標準的な方法は 1 つだけです: realloc()

申し訳ありませんが、C 標準にはその趣旨の言語は含まれていません。

次に realloc() を再度使用すると、ブロックが元に戻り、少なくともダングリング ポインターが指すオブジェクト型をカバーします。どちらの場合も、realloc() はメモリ ブロックを移動しませんでした。

そうなるかどうかはわかりません...標準はそのようなことを保証していません。特に、より小さなサイズに再割り当てすると、ほとんどの実装では、短縮されたブロックの直後にメモリが変更されます。元のサイズに再割り当てすると、追加された部分にガベージが発生し、縮小される前の状態にはなりません。一部の実装では、一部のブロック サイズがそのブロック サイズのリストに保持されます。別のサイズに再割り当てすると、まったく異なるメモリが得られます。また、複数のスレッドを持つプログラムでは、解放されたメモリを 2 つの再割り当ての間の別のスレッドに割り当てることができます。この場合、より大きなサイズの再割り当ては、オブジェクトを別の場所に強制的に移動させます。

ダングリングポインタは再び有効ですか?

上記を参照; 無効は無効です。戻ることはありません。

これは非常にまれなケースであるため、C または C++ 標準を解釈して理解する方法がよくわかりません。

それはどんな種類のコーナーケースでもなく、標準で何を見ているのかわかりません。これは、解放されたメモリの内容が不確定であり、それへの、またはその中へのポインタの値も不確定であることを明確に示しています。それらは後の再割り当てによって魔法のように復元されると主張しています。

最新の最適化コンパイラは、未定義の動作を認識し、それを利用するように作成されていることに注意してください。文字列を再割り当てするとすぐにoverwrite無効になり、コンパイラはそれを自由に破棄できます...たとえば、コンパイラが一時またはパラメータの受け渡しのために再割り当てするレジスタにある可能性があります。オブジェクトの有効期間が終了すると、オブジェクトへのポインターが無効になることについて標準が非常に明確であるため、コンパイラーがこれを行うかどうかにかかわらず、それは可能です。

于 2014-09-27T09:01:37.717 に答える