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

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

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

但是,如果内存再次对同一分配有效,该怎么办? 只有一种标准方式可以实现: realloc() 。 如果你有一个指向malloc() ‘内存块中偏移量> 1某个位置的指针,那么使用realloc()将块缩小到小于你的偏移量,显然你的指针变得无效。 如果你再使用realloc()再次将块增长回至至少覆盖悬空指针指向的对象类型,并且在任何情况下都没有realloc()移动内存块,悬空指针是否再次有效?

这是一个极端的案例,我真的不知道如何解释C或C ++标准来解决它。 以下是显示它的程序。

 #include  #include  #include  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; } 

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

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

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

从第7.22.3.5节开始:

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

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

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

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

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

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

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

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

悬空指针又有效吗?

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

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

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

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

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

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

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

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

C语言不健全,通常由程序员来维护其不变量。 如果不这样做将破坏与编译器的隐式契约,并可能导致生成错误的代码。

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