0

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

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

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
}
  • 这样做有什么问题吗?
  • 我可以用数字存储地址吗?就像在无符号整数中一样?默认类型是什么?
4

5 回答 5

4

管理指向可能被释放的对象的指针的最佳实践是将每个指针转换为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 *和将 a 转换void *为的行为uintptr_t

于 2019-06-20T22:04:38.730 回答
1

根据C 标准

转换说明符及其含义是:

...

p

参数应该是一个指向 void 的指针。指针的值以实现定义的方式转换为打印字符序列。

因此,要完全符合 C 标准,您需要转换heapvoid *

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

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

于 2019-06-20T21:18:01.083 回答
1

你的代码很好。

如果您只想打印指针本身的值,那么指针是否有效1都没有关系。毕竟,您可以毫无问题地打印 的值NULL根据定义,它是一个无效的指针值。%p

*heap显然,如果您尝试在内存被释放后或之后做某事heap[i],您会遇到未定义的行为并且任何事情都可能发生,但是您可以检查或打印heap(或address)您想要的所有内容,而不会出现任何问题。


  1. 其中“有效”表示“指向该对象生命周期内的对象”。

于 2019-06-20T21:50:18.880 回答
0

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

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

于 2019-06-20T20:56:43.500 回答
-2

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

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

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

注意标准 C free() 调用的原型:void free(void *ptr)。在调用的地方,ptr将在自由调用前后保持不变——它是按传递的。

“使用”指针将/可能以各种方式失败,如果使用我们的意思是在 free() 调用之后尊重它。打印指针的值不会尊重它。即使指针值表示是实现定义的,也可以使用 %p 调用 printf 来打印指针的(值)。

"*...但是,由于该规则存在,甚至更少奇异的 C 实现可能会利用它进行优化,因此 C 编译器可以实现 free(x); printf("%p", (void *) x);等价于 free(x); printf("%p", (void ) NULL);,例如... "

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

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

正如您在此处建议的那样,它没有什么可以优化的,并且它(编译器)无法推断出 myptr 的

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

(我的)编辑:

除非,一些“异国情调”的实现,内存指针被实现为结构,并且它被强制转换为/从“用户”的 void*.. 尝试打印时用户的堆值将无效或为空.. 但是那么每个接受 void* 作为指向内存的指针的 C 调用都需要区分哪个 void* 指向的堆不是——比如 memcpy、memset ——然后这会变得很忙......

于 2019-06-20T23:10:40.097 回答