0

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
}
  • Is there any problem doing it this way?
  • Can I store the address numerically? Like in an unsigned integer? What is the default type?
LeoVen
  • 632
  • 8
  • 18

5 Answers5

4

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.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I was going to ask for a clarification if you can directly cast any pointer to uintptr_t and found some relevant discussion here: https://stackoverflow.com/questions/34291377/converting-a-non-void-pointer-to-uintptr-t-and-vice-versa – LeoVen Jun 21 '19 at 00:25
  • @LeoVen: Thanks, updated with `void *` as an intermediate step. – Eric Postpischil Jun 21 '19 at 01:22
  • `uintptr_t` is an optional type. I don't think there is a guaranteed-to-exist, strictly-conforming path to printing the value of a freed pointer, unless something like `unsigned long long` is always able to hold the value of a pointer. – Andrew Henle Jun 21 '19 at 10:03
  • @AndrewHenle: Nonetheless, it is the best option: If code uses a pointer when it is indeterminate, we do not know what will happen. If code uses `uintptr_t`, we know what will happen: Either it will compile and work or it will not compile, alerting us to the fact that customizations for this particular implementation are needed. – Eric Postpischil Jun 21 '19 at 10:58
1

Per 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.

So, to be fully conforming to the C standard, you need to cast heap to void *:

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

The fact that you have used free() on the pointer doesn't mean you can't print the pointer's value on most implementations (See comments on other answers for why that may be true) - it just means you can't dereference the pointer.

Community
  • 1
  • 1
Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
1

Your code is fine.

It doesn't matter whether the pointer is valid1 or not if all you want to do is print the value of the pointer itself. After all, you can print the value of NULL, which by definition is an invalid pointer value, using %p with no problem.

Obviously, if you try to do something with *heap or heap[i] after the memory has been deallocated, you run into undefined behavior and anything can happen, but you can examine or print out heap (or address) all you want with no issue.


  1. Where "valid" means "pointing to an object within that object's lifetime".

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Per the C standard, a pointer to an object itself becomes indeterminate when the object’s lifetime ends. So, after `free(x)`, attempting to print `x` is not defined by the C standard. Many C implementations will not cause a problem in this regard (the reason for it in the standard is to accommodate implementations in which a pointer has a “live” part—using it requires looking up data that may change or vanish upon deallocation of the pointed-to object). However, it would be best to convert the pointer to `uintptr_t` before performing the `free` and using that value for tracking and printing. – Eric Postpischil Jun 20 '19 at 21:56
  • "can print the value of NULL, which by definition is an invalid pointer value, using %p with no problem." --> Not certainly. `NULL` is not specified in C to be a pointer. It may be some integer type with the value of 0. Assigning/comparing `NULL` to a pointer is well specified, yet `printf("%p", NUULL);` is also not certainly OK. `sizeof(void *)` may differ from `sizeof(NULL)` – chux - Reinstate Monica Jun 21 '19 at 01:33
  • @EricPostpischil But `uintptr_t` is an optional type. (This part of C is a mess...) – Andrew Henle Jun 21 '19 at 10:01
  • @AndrewHenle: Nonetheless, it is the best option: If code uses a pointer when it is indeterminate, we do not know what will happen. If code uses `uintptr_t`, we know what will happen: Either it will compile and work or it will not compile, alerting us to the fact that customizations for this particular implementation are needed. – Eric Postpischil Jun 21 '19 at 10:57
0

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

In your code you get the address if the local function parameter not it's value.

0___________
  • 60,014
  • 4
  • 34
  • 74
-2

Because of my limited rating, I cannot yet comment on posts by others, so using "answer" while this is not answer but comment.

The answer/ comment above by Eric Postpischil needs to be peer reviewed. I don't agree or understand his reasoning.

" ... 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 .."

Note the prototype of standard C free() call : void free(void *ptr). At the place of invocation, the ptr value will stay the same before and after the free call - it is passed by value.

To "use" the pointer will/may fail in various ways if by use we mean deference it after the free() call. Printing the value of the pointer does not deference it. Also printing the (value of) the pointer with a call to printf using %p shall be fine even if pointer value representation is implementation defined.

"*...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. ..."

Even if it (compiler) do so, I cannot easily see what would this optimize, a call to a special minimized printf with a value 0 for pointer? Also consider something like:

extern void my_free(void* ptr); //defined somewhere.. my_free( myptr ); printf("%p", myptr );

It would have nothing to optimize as you suggest here, and it(compiler) could deduce nothing about the value of myptr.

So no, I cannot agree with your interpretation of the C standard at this stage.

(my)EDIT:

Unless, with some 'exotic' implementations with a memory pointer being implemented as a struct, and it's cast to/from "user"s void*.. Then the user's heap value would be invalid or null when trying to print it.. But then every C call that accepts void* as a pointer to a memory would need to differentiate which void* points to heap which doesn't - say memcpy, memset , - and this then gets quite busy ...

v01d
  • 189
  • 1
  • 12
  • 1
    First, you did not address the explicit statement of the C standard: “The value of a pointer becomes indeterminate…” Note it says the **value** becomes indeterminate. Your answer goes on to say “the ptr *value* will stay the same.” But that is false; the C standard explicitly says the value becomes indeterminate—we do not know what its value will be. – Eric Postpischil Jun 20 '19 at 23:40
  • Second, the C standard additionally tells us that merely using the value results in undefined behavior: Non-normative clause J.2 lists things that caused undefined behavior, and it includes “The value of a pointer to an object whose lifetime has ended is used (6.2.4).” – Eric Postpischil Jun 20 '19 at 23:40
  • Third, you have not followed the explanation I gave. As an example, a C implementation might support an addressing scheme that uses auxiliary storage as well as main memory. To deal with this, a pointer is not an address in memory but is a handle into a database of objects. When the pointer is used to access an object, the handle is looked up in the database, and the data is loaded into main memory if necessary, and then the data is accessed. But even when the pointer is merely printed, the handle is looked up, and full information about the auxiliary storage location is printed… – Eric Postpischil Jun 20 '19 at 23:43
  • … Now, suppose in an implementation like that, you have `free(x); printf("%p", (void *) x);`. When the `free` is executed, the handle is deleted from the database. Then, when the `printf` execute, the bytes in `x` are used to look up the handle. Unfortunately, it no longer exists in the database. Maybe the program detects the error and crashes. Maybe it returns garbage data. Maybe it returns a null address. The C standard does not tell us what will happen; it merely says the pointer value is indeterminate. – Eric Postpischil Jun 20 '19 at 23:44
  • Fourth, your statement that “I cannot easily see what this would optimize; a call to a special minimized printf with a value 0 for pointer” does not support your case. Sure, I have seen optimizations for `printf` with constant values. `printf("%d", 34);` may be changed to `fputs("34", stdout);`, which is faster since `printf` does not have to parse a format string at run-time. – Eric Postpischil Jun 20 '19 at 23:49
  • Fifth, your example of a case in which the `printf` is not optimized away is irrelevant—there is no assertion that the desired effect of the code will always be broken by optimization permitted by the C standard, merely that it may be broken. Showing a case where it is not broken does not disprove that it may be broken. – Eric Postpischil Jun 20 '19 at 23:49
  • 2
    In summary, let the C standard speak for itself: “The value of a pointer becomes indeterminate…” That is **the** specification of the C semantics for this situation; it is the **authoritative** statement on the matter. The rest is merely explanation for people who do not understand it yet. – Eric Postpischil Jun 20 '19 at 23:50
  • Well, I sorta thought afterwards about a more complex heap pointer.. . But very much still surprised I cannot rely on my void* value staying same after a call where I passed it by value not by ptr (to ptr) ... Same I mean, from the C lib user interface perspective, irrespective of what the library implementation does behind it.. A bit of a cold water shock, tbh .. And yes, I cannot claim I understand the standard or read it regularly , last one I partially skimmed through was C99 – v01d Jun 21 '19 at 00:03
  • Overall, apologies that you have had to type all this, the gains are all mine, your time lost (but you got points up at least .. ) – v01d Jun 21 '19 at 00:06