1

当您释放内存时,指向该内存的指针会发生什么?它们会立即失效吗?如果它们后来再次生效会怎样?

当然,指针变为无效然后再次变为“有效”的通常情况是其他对象被分配到恰好是之前使用的内存中,如果您使用指针访问内存,那显然是未定义的行为。悬空指针内存几乎覆盖了第 1 课。

但是,如果内存对于相同的分配再次变得有效怎么办?只有一种标准方式可以做到这一点:realloc(). 如果您有一个指向malloc()offset 处的 'd 内存块中某处的指针> 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 返回的对象与以前的对象不同;free这与执行 a然后 a没有什么不同malloc,并且不能保证两个对象具有相同的地址,即使新大小 <= 旧大小......并且在实际实现中它们通常不会因为对象不同的大小来自不同的空闲列表。

如果它们后来再次生效会怎样?

没有这种动物。有效性不是发生的某个事件,它是 C 标准放置的抽象条件。您的指针可能碰巧在某些实现中起作用,但是一旦您释放了它们指向的内存,所有的赌注都将被取消。

但是,如果内存对于相同的分配再次变得有效怎么办?只有一种标准方式可以做到这一点:realloc()

抱歉,不,C 标准不包含任何语言。

如果您然后再次使用 realloc() 将块重新增长到至少覆盖悬空指针指向的对象类型,并且在任何情况下 realloc() 都不会移动内存块

你不知道它是否会......标准不保证任何这样的事情。值得注意的是,当您重新分配到较小的大小时,大多数实现会立即在缩短的块之后修改内存;重新分配回原始大小会在添加的部分有一些垃圾,它不会是缩小之前的样子。在一些实现中,一些块大小被保存在该块大小的列表中;重新分配到不同的大小会给你完全不同的内存。并且在具有多个线程的程序中,任何释放的内存都可以在两个重新分配之间的不同线程中分配,在这种情况下,较大大小的重新分配将被迫将对象移动到不同的位置。

悬空指针是否再次有效?

往上看; 无效是无效的;回不去了。

这是一个极端情况,我真的不知道如何解释 C 或 C++ 标准来弄清楚。

这不是任何极端情况,我不知道您在标准中看到了什么,这很清楚释放的内存具有不确定的内容,并且任何指向或指向它的指针的值也是不确定的,并且没有声称它们被后来的 realloc 神奇地恢复了。

请注意,编写现代优化编译器是为了了解未定义的行为并利用它。一旦你重新分配字符串,overwrite它就无效了,编译器可以随意丢弃它......例如,它可能在编译器为临时或参数传递重新分配的寄存器中。无论任何编译器都这样做,它都可以,正是因为标准非常清楚,当对象的生命周期结束时,指向对象的指针变得无效。

于 2014-09-27T09:01:37.717 回答
0

如果您然后再次使用 realloc() 将块重新增长到至少覆盖悬空指针指向的对象类型,并且在这两种情况下 realloc() 都没有移动内存块,那么悬空指针是否再次有效?

不会。除非realloc()返回空指针,否则调用将终止分配对象的生命周期,这意味着指向它的所有指针都将变为无效。如果realloc()成功,则返回对象的地址。

当然,它可能只是与旧地址相同。在这种情况下,使用指向旧对象的无效指针来访问新对象通常适用于 C 语言的非优化实现。

但是,它仍然是未定义的行为,并且实际上可能会因积极优化编译器而失败。

C 语言是不健全的,通常由程序员来维护它的不变量。否则将破坏与编译器的隐式契约,并可能导致生成不正确的代码。

于 2014-09-27T08:45:28.983 回答
-1

这取决于您对“有效”的定义。你已经完美地描述了这种情况。如果您想认为“有效”,那么它就是有效的。如果您不想认为“有效”,那么它就是无效的。

于 2014-09-27T08:36:15.967 回答