在指向 C 中的外部变量的指针上调用 free

Calling free on a pointer to an extern variable in C

我想知道 C 程序在指向外部变量的指针上调用 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 的定义。我的问题是这个定义是否真的排除了这样的程序可能在符合标准的系统上可靠地工作的可能性(而不仅仅是 "it can do anything if the behavior is undefined")。所以我想知道您是否可以想到一个 configuration/system/whatever 程序不会暴露未定义行为的地方。换句话说:是否存在根据 C 标准正确定义对 free 的调用的条件?

您应该只在先前由 malloccallocreallocaligned_alloc 分配的指针上调用 free。不然是不是"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);

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

C11 标准 -- 7.22.3.3

C 标准对此没有歧义。引用文档 N1570,最接近 C11 的近似值可在线免费获得,第 7.22.3.3 节第 2 节(free 的规范):

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

"Memory management functions"列于7.22.3开头:malloccallocreallocaligned_alloc。 (一个实现可以添加更多这样的功能,例如 posix_memalign——阅读底部的注释!)

现在,"the behavior is undefined" 授权实现在情况发生时做 任何事情。崩溃很常见,但 MSVC 的运行时库完全有权检测指针在 "heap" 之外并且什么也不做。尝试调试模式:可能有一种模式会使程序崩溃。

作为代码验证工具的作者,应该最严格:如果你不能证明一个指针传递给 free 的是 NULL 或先前由内存管理函数返回的值,将其标记为错误。


附录:有些令人困惑的 "or if the space has been deallocated..." 子句旨在禁止双重释放:

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

双补:

Are there conditions under which the call to free here would be defined properly according to the C standard?

没有。如果有这样的条件,它必须出现在标准的文本中作为我在这个答案开头引用的规则的例外,并且没有任何这样的例外。

然而,有一个微妙的变化,答案是 'yes':

Could there be an implementation of C under which the behavior of the program shown is always well-defined?

例如,其中 free 被记录为不执行任何操作的实现,无论其输入如何,都符合条件,甚至不是一个疯狂的想法——许多程序可以从不 中逃脱毕竟调用 free。但是根据 C 标准,程序的行为仍然是未定义的;只是 C 实现选择了明确定义这个特定的 UB 场景。

(从语言律师的角度来看,该语言的每个实现扩展 都是一个使 UB 场景定义明确的实现案例。甚至几乎无处不在的东西,如 #include <unistd.h>.)

当您在之前未由 malloc、calloc 或 realloc 分配的指针上调用 free 时,行为未定义。这意味着它在不同的工具链中可以有不同的行为,在同一程序的不同时间也可以有不同的行为。

它是否生成带有错误消息的崩溃取决于该特定平台的库实现是否检查指针的有效性。由于检查有效性可能具有非零的性能成本,因此并非所有程序都会这样做。此外,即使实现进行了一些有效性检查,它也可能无法检测到所有无效指针。

一个实现可能会检测到指向静态内存区域的指针不能是有效的堆指针;其他人可能会将其视为有效的堆指针,并尝试将 "free" 内存添加回堆,从而破坏堆以及过程中静态内存中与外部值相邻的值。该程序可能 运行 暂时还好,但堆和静态内存已损坏。

这就是为什么有可供 C 语言使用的替代的、经过检测的堆实现来提供额外的检查。

Chapter and verse

7.22.3.3 The free function
...
2 The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.

强调我的。

由于行为未定义,任何 编译器或运行 时间环境采取的行动是可接受的。它可能会拒绝带有诊断的代码,它可能会翻译带有诊断的代码,它可能会在没有诊断的情况下翻译代码,您的代码可能会在 运行 时崩溃,您的代码可能看起来 运行 没有如有任何问题,您的代码可能会调用 Rogue 等