realloc()悬空指针和未定义的行为

Myr*_*ria 1 c pointers realloc undefined-behavior dangling-pointer

当你释放内存时,指向内存的指针会发生什么?他们立即变得无效吗?如果他们以后再次有效会怎么样?

当然,指针变为无效然后再次变为"有效"的通常情况是将其他对象分配到之前使用的内存,如果使用指针访问内存,那么这显然是未定义的行为.悬空指针内存覆盖了第1课,差不多.

但是如果内存再次对同一个分配有效呢?只有一种标准方式可以实现:realloc().如果你有一个指向malloc()偏移量'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;
}
Run Code Online (Sandbox Code Playgroud)

Jim*_*ter 7

当你释放内存时,指向内存的指针会发生什么?他们立即变得无效吗?

当然是.从C标准第6.2.4节:

对象的生命周期是程序执行的一部分,在此期间保证为其保留存储.存在一个对象,具有一个常量地址,并在其整个生命周期内保留其最后存储的值.如果在其生命周期之外引用对象,则行为未定义.当指针指向(或刚刚过去)的对象到达其生命周期的末尾时,指针的值变得不确定.

从第7.22.3.5节开始:

realloc函数释放ptr指向的旧对象,并返回指向具有size指定大小的新对象的指针.新对象的内容应与解除分配之前的旧对象的内容相同,直到新旧大小中的较小者为止.新对象中超出旧对象大小的任何字节都具有不确定的值.

注意对旧对象新对象的引用......按照标准,从realloc返回的是以前不同的对象 ; 它与a free和a一样没有什么不同malloc,并且不能保证这两个对象具有相同的地址,即使新的大小<=旧的大小......并且在实际实现中它们通常不会因为从不同的免费列表中提取不同的大小.

如果他们以后再次有效会怎么样?

没有这样的动物.有效性不是发生的事件,它是C标准所放置的抽象条件.你的指针可能恰好在某些实现中起作用,但是一旦你释放它们指向的内存,所有的赌注都会被取消.

但是如果内存再次对同一个分配有效呢?只有一种标准方式可以实现:realloc()

对不起,不,C标准不包含任何语言.

如果你再使用realloc()再次将块增长回至至少覆盖悬空指针指向的对象类型,并且在任何情况下都没有realloc()移动内存块

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

是悬空指针再次有效吗?

往上看; 无效无效; 回不去了.

这是一个极端的案例,我真的不知道如何解释C或C++标准来解决它.

它不是任何一种极端情况,我不知道你在标准中看到了什么,很明显,释放的内存具有不确定的内容,并且指向或进入它的任何指针的值也是不确定的,并且不会声称他们被后来的realloc神奇地恢复了.

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