cpointersloggingprintingpointer-address

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


I'm making a logging utility that tracks allocations and deallocations inside a library I made.

My program didn't crash but I'm still skeptical about my approach.

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
}

Solution

  • The best practice for managing pointers to objects that may be freed is to convert each pointer to uintptr_t while it is still valid (points to an object before it is freed) and to use those uintptr_t values for printing and other purposes.

    Per C 2018 6.2.4 2, “The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.” A primary reason for this rule is to permit C implementations in which the bytes in the pointer do not contain the direct memory address but contain information that is used to look up address information in various data structures. When an object is freed, although the bytes in the pointer do not change, the data in those structures may change, and then attempting to use the pointer can fail in various ways. Notably, attempting to print the value can fail because the former address is no longer available in the data structures. However, since that rule exists, even less exotic C implementations may take advantage of it for optimization, so a C compiler can implement free(x); printf("%p", (void *) x); equivalently to free(x); printf("%p", (void *) NULL);, for example.

    To work around this, you can save a pointer as a uintptr_t value and use that uintptr_t value for further use, including printing:

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

    Note that, to be strictly conforming, we first convert the pointer to void * and then to uintptr_t. This is simply because the C standard does not explicitly specify the behavior of converting any pointer to uintptr_t but does specify the behavior of converting a pointer to an object to void * and of converting a void * to uintptr_t.