打印已从堆中释放的变量地址的正确方法是什么?

What is the correct way to print the address of a variable already deallocated from the heap?

我正在制作一个日志实用程序,用于跟踪我创建的库中的分配和释放。

我的程序没有崩溃,但我仍然对我的方法持怀疑态度。

void my_free(struct my_type *heap)
{
    if (!heap)
        logger("Fatal error: %s", "Null pointer parameter");

    // I leave this here on purpose for the application to crash
    // in case heap == NULL
    free(heap->buffer);

    void *address = heap;

    free(heap);

    logger("Heap at %p successfully deallocated", address);
    // since heap->buffer is always allocated/deallocated together with
    // heap I only need to track down the heap address
}

void *address = &heap; -->> void *address = heap;

在您的代码中,如果局部函数参数不是它的值,您将获得地址。

根据 the C standard:

The conversion specifiers and their meanings are:

...

p

The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.

所以,为了完全符合C标准,你需要将heap转换为void *

logger("Heap at %p successfully deallocated", ( void * ) address);

您在指针上使用 free() 的事实并不意味着您不能在大多数实现中打印指针的值(请参阅其他答案的评论以了解为什么这可能是真的)——它只是意味着你不能取消引用指针。

你的代码没问题。

指针是否有效1 无关紧要,只要您只想打印指针本身的值即可。毕竟,您可以打印 NULL 的值,根据定义 是无效指针值,使用 %p 没有问题。

显然,如果你在内存被释放后尝试用 *heapheap[i] 做一些事情,你 运行 会进入未定义的行为,任何事情都可能发生,但你可以检查或打印出 heap(或 address)所有你想要的,没有问题。


  1. 其中 "valid" 表示 "pointing to an object within that object's lifetime"。

管理指向可能被释放的对象的指针的最佳做法是在每个指针仍然有效时将其转换为 uintptr_t(指向一个对象,然后再释放)并使用那些 uintptr_t 用于打印和其他目的的值。

根据 C 2018 6.2.4 2,“当指针指向(或刚刚过去)的对象达到其生命周期结束时,指针的值变得不确定。”此规则的一个主要原因是允许 C 实现,其中指针中的字节不包含直接内存地址,但包含用于在各种数据结构中查找地址信息的信息。当一个对象被释放时,虽然指针中的字节没有改变,但那些结构中的数据可能会改变,然后尝试使用指针可能会以各种方式失败。值得注意的是,尝试打印该值可能会失败,因为以前的地址在数据结构中不再可用。然而,由于该规则存在,即使是不那么奇特的 C 实现也可以利用它进行优化,因此 C 编译器可以实现 free(x); printf("%p", (void *) x); 等同于 free(x); printf("%p", (void *) NULL);,例如。

要解决此问题,您可以将指针保存为 uintptr_t 值并使用该 uintptr_t 值进一步使用,包括打印:

#include <inttypes.h>
#include <stdint.h>
...
uintptr_t ux = (uintptr_t) (void *) x;
free(x);
…
printf("%" PRIxPTR, ux);

注意,为了严格遵守,我们首先将指针转换为void *,然后再转换为uintptr_t。这仅仅是因为 C 标准没有明确指定将任何指针转换为 uintptr_t 的行为,但确实指定了将指向对象的指针转换为 void * 以及将 void * 转换为 void * 的行为至 uintptr_t.

由于我的评分有限,我还不能评论其他人的帖子,所以使用 "answer" 虽然这是 不是 回答而是评论。

Eric Postpischil 上面的回答/评论需要同行评审。 我不同意也不理解他的推理。

" ... 当一个对象被释放时,虽然指针中的字节没有改变,但那些结构中的数据可能会改变,然后尝试使用指针可能会以各种方式失败. 值得注意的是,尝试打印该值可能会失败,因为 .."

注意标准 C free() 调用的原型:void free(void *ptr)。在调用处,ptr value 将在 free 调用前后保持不变 - 它由 value 传递。

到 "use" 指针 will/may 以各种方式失败,如果我们的意思是 deference 在 free() 调用之后。 打印指针的值 并不会影响它。 即使指针值表示是实现定义的,使用 %p 调用 printf 也可以打印指针(的值)。

"*...但是,由于该规则存在,即使是不那么奇特的 C 实现也可以利用它进行优化,因此 C 编译器可以实现 free(x); printf("%p", (void *) x); 等同于 free(x); printf("%p", (void ) NULL);,例如..."

即使它(编译器)这样做,我也无法轻易看出这会优化什么,调用一个指针值为 0 的特殊最小化 printf? 还要考虑类似的东西:

extern void my_free(void* ptr); //在某处定义.. my_free( myptr ); printf("%p", myptr);

它不会像您在此处建议的那样进行优化,并且它(编译器)无法推断出任何有关 myptr.value 的信息。

所以不,我不能同意你现阶段对 C 标准的解释。

(我的)编辑:

除非,某些 'exotic' 实现将内存指针实现为结构,并且将其转换为 to/from "user"s void*.. 那么用户的堆值将是尝试打印时无效或为空.. 但是,每个接受 void* 作为指向内存的指针的 C 调用都需要区分哪些 void* 指向堆,哪些不指向堆——比如 memcpy、memset ,——然后这会变得很忙……