我想知道一个C程序在指向extern变量的指针上调用free的行为。背景是我是分析C代码的验证者的开发者,我想知道如果遇到这种情况我的验证者应该做什么(例如,说明为什么程序未定义 - 如果是的话)。
为了通过实验找出行为,我尝试运行以下C程序:
#include <stdlib.h>
extern int g = 1;
int main() {
int *ptr = &g;
free(ptr);
return g;
}
在Debian GNU / Linux 7系统上,该程序崩溃并显示一条错误消息,指出传递给free的指针无效。在Windows 7系统上,我可以运行此程序而不会出现任何错误消息。你知道这个观察的解释吗?
更新
我确实读过free
的定义。我的问题在于这个定义是否真正排除了这样一个程序是否可以在一个符合标准的系统上可靠地工作的可能性(而不仅仅是#34;如果行为未定义,它可以做任何事情&#34;)。所以我想知道你是否能想到一个配置/系统/这个程序没有暴露未定义行为的地方。换句话说:是否存在根据C标准正确定义free
的调用的条件?
答案 0 :(得分:5)
C标准对此毫不含糊。引用文件N1570,最接近C11免费在线提供,第7.22.3.3节第2段(free
的规范):
free
函数会导致ptr
指向的空格 被解除分配,即可用于进一步分配。如果ptr
是空指针,不会发生任何操作。否则,如果参数与先前由内存管理函数返回的指针不匹配,或者如果通过调用free
或realloc
释放了空间,则 行为未定义。
“内存管理功能”列在7.22.3的开头:malloc
,calloc
,realloc
和aligned_alloc
。 (实现可以添加更多此类功能,例如posix_memalign
- 阅读底部的注释!)
现在,“行为未定义”许可实施在情况发生时执行任何。崩溃是常见的,但MSVC的运行时库完全有权检测到指针在“堆”之外并且什么都不做。试验调试模式:可能会有一种模式会导致程序崩溃。
作为代码验证工具的作者,你应该是最严格的:如果你不能证明传递给free
的指针是NULL
或先前由内存管理函数返回的值,将其标记为错误。
附录:有些令人困惑的“或者如果空间已被解除分配......”条款旨在禁止双重释放:
char *x = malloc(42);
free(x); // ok
free(x); // undefined behavior
...但要注意内存重用:
char *x = malloc(42);
uintptr_t a = (uintptr_t)x;
free(x);
x = malloc(42);
uintptr_t b = (uintptr_t)x;
observe(a == b); // un*specified* behavior - must be either true or false,
// but no guarantee which
free(x); // ok regardless of whether a == b
双重附录:
是否有条件可以根据C标准正确定义
free
的调用?
没有。如果存在这样的情况,则必须在标准的文本中出现作为我在本答案开头引用的规则的例外,并且没有任何此类例外。< / p>
然而,有一个微妙的变化,答案是'是': 是否存在C的实现,其中所显示的程序的行为始终是明确定义的? 例如,一个实现,其中
free
被记录为什么都不做,无论其输入如何,都有资格,甚至不是一个疯狂的想法 - 许多程序可以通过永远不会调用 free
。但是根据C标准
(从语言 - 律师的角度来看,语言的每个实现扩展都是实现的一个案例,使得UB场景得到了很好的定义。甚至像#include <unistd.h>
这样的无处不在的事物。 )
答案 1 :(得分:4)
您只应在之前由free
或malloc
或calloc
或realloc
分配的指针上调用aligned_alloc
。否则,它是否“extern'd”无关紧要。
更新#1
它并不总是没有错误地运行,也检查valgrind
的输出是什么:
==21569== Invalid free() / delete / delete[] / realloc()
==21569== at 0x4C29D2A: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==21569== by 0x4004FA: main (main.c:7)
==21569== Address 0x6008e8 is 0 bytes inside data symbol "g"
更新#2
void free(void *ptr);
free
函数导致ptr
指向的空间被释放,即可用于进一步分配。如果ptr
是空指针,则不执行任何操作。否则,如果参数与先前由内存管理函数返回的指针不匹配,或者如果通过调用free
或realloc
释放了空格,则行为未定义。
C11标准 - 7.22.3.3
答案 2 :(得分:2)
当你在一个之前没有被malloc,calloc或realloc分配的指针上调用free时,行为是未定义的。这意味着它可以在不同的工具链中具有不同的行为,并且在同一程序中的不同时间也可以有不同的行为。
是否使用错误消息生成崩溃取决于特定平台的库实现是否检查指针的有效性。由于检查有效性可能具有非零的性能成本,因此并非所有程序都会这样做。此外,即使实现进行了一些有效性检查,它也可能无法检测到所有无效指针。
一个实现可以检测到指向静态存储区的指针不能是有效的堆指针;其他人可能会将其视为有效的堆指针并尝试添加&#34; free&#34;内存返回堆,破坏堆以及进程中静态内存中与extern值相邻的值。该程序可能运行正常,一段时间,堆损坏和静态内存。
这就是为什么有可用于C的备用,检测的堆实现提供额外检查的原因。
答案 3 :(得分:2)
7.22.3.3free
功能
...
2free
函数导致ptr
指向的空间被释放,即 可供进一步分配。如果ptr
是空指针,则不执行任何操作。否则,如果 该参数与先前由内存管理返回的指针不匹配 功能,或者如果通过调用free
或realloc
,取消分配空间 行为未定义。
强调我的。
由于行为未定义,编译器或运行时环境采取的任何操作都是可接受的。它可能会拒绝带有诊断的代码,它可能会使用诊断转换代码,它可能会在没有诊断的情况下转换代码,您的代码可能会在运行时崩溃,您的代码可能看起来没有任何问题,您的代码可能会调用Rogue,等。