6

I'm trying to explain to someone why they have a dangling pointer and how free actually works (and that pointers are values and thus are passed-by-value), but for that I think I need a way to print pointers that isn't "indeterminate" (as is the case with printf("%p", ptr)).

Would memcpy do the trick?

char buf1[sizeof(char *)];
char buf2[sizeof(char *)];
char *malloced = malloc(10);
memcpy(buf1, &malloced, sizeof(char *));
free(malloced);
memcpy(buf2, &malloced, sizeof(char *));
for (int i=0; i<sizeof(char *); i++) {
    printf("%hhd %hhd / ", buf1[i], buf2[i]);
}
SoniEx2
  • 1,864
  • 3
  • 27
  • 40
  • 3
    Pretty sure there’s no legal way to do anything with a dangling pointer, so just go ahead and `%p`. – Ry- Jul 14 '18 at 23:24
  • I can't figure out what you mean by "indeterminate". Can you clarify the problem you're trying to solve? What do you want to print out? What more can you say about a dangling pointer other than that it's dangling? – David Schwartz Jul 14 '18 at 23:24
  • @DavidSchwartz I want a visual aid that shows the pointer's value is the same before and after the call to free (since pointers are pass-by-value, so you're copying the value into free, and free can't modify your copy of the value). – SoniEx2 Jul 14 '18 at 23:26
  • @DavidSchwartz Reading a pointer value (let alone dereferencing it) after it was `delete`d is UB, and OP is trying to work around that. – HolyBlackCat Jul 14 '18 at 23:26
  • it's UB but I don't see a lot of implementation where this would not "work" – Stargateur Jul 14 '18 at 23:33
  • Is the point to show that this is true by writing code that only works because you already know this is true? Or is the point to detect whether or not this is true with code that will work whether or not it's true? The former is pointless circularity. The latter is not possible. So whatever you're outer problem is, it seems like you've gone off track in trying to solve it. – David Schwartz Jul 14 '18 at 23:46
  • Calling `free` might set the pointer variable's representation to null or anything else – M.M Jul 14 '18 at 23:47
  • 1
    `free` is defined as `void free(void *ptr)` not `void free(void **ptr_to_ptr)`. it cannot actually change the pointer as far as I'm aware. – SoniEx2 Jul 14 '18 at 23:50
  • @SoniEx2 You limit you to C classic rule, nothing prevent implementation to implement pointer as they like, so nothing prevent implementation to change the value of ptr. Some implementation do it. – Stargateur Jul 14 '18 at 23:56
  • @SoniEx2 `free` and `realloc` do not receive `void**` arguments, but they make all copies of the argument pointer indeterminate. The argument of these functions, if it happens to be an lvalue, is only a particular copy to which this applies. See https://blog.regehr.org/archives/767#comment-4717 for an example with `realloc`. – Pascal Cuoq Jul 14 '18 at 23:56
  • It might be able to do whatever it wants according to the spec (including act completely like the pointer variable was passed by reference), but I don’t volunteer for that reading. Maybe you should demonstrate with another function or find out what point you’re really trying to get across. – Ry- Jul 14 '18 at 23:57
  • @Ry- If you allow using `realloc` instead of `free`, then https://blog.regehr.org/archives/767#comment-4717 is an example that shows that functions that the standard allows to make all copies of a pointer indeterminate can actually do it. – Pascal Cuoq Jul 14 '18 at 23:58
  • I see little value of printing the data pointed to by dangling pointers, for demonstration purposes or anything else. By definition, there is no way to do so in a predictable manner (e.g. values changing or not, a crash occurring or not). Any behaviour you will demonstrate will be specific to your *implementation at a particular time*, and is not relevant to a discussion of C. I've lost count of the number of beginners, who have been shown such "demonstrations", who believe nonsense such as "using a freed or NULL pointer always results in a crash". – Peter Jul 15 '18 at 00:46
  • @SoniEx2 The value is a combination of two things -- the bit pattern and the mapping of bit patterns to values. Even if the function can't change the bit pattern, it can change the mapping of bit patterns to values, thus changing the value. The standard specifically says you can't make assumptions about the values of dangling pointers. So why are you? – David Schwartz Jul 16 '18 at 05:20
  • @DavidSchwartz so wouldn't memcpy avoid that problem? – SoniEx2 Jul 16 '18 at 11:28
  • @SoniEx2 I'm not really sure how. How do you think that would help? You'd `memcpy` the bit pattern and then what? If you look at the bit pattern, it doesn't tell you whether or not the value changed. You could tell whether or not the bit pattern changed, but you already know it can't. – David Schwartz Jul 17 '18 at 21:10

1 Answers1

9

According to a strict reading of the C standards, you can't do anything with a dangling pointer: “indeterminate”, the state of memory that holds a dangling pointer, also describes the contents of uninitialized automatic variables, and these may have different values if you read them twice in a row(*).

The only way around the issue is to convert the pointer to uintptr_t while it is still valid. The result of the conversion is an integer and has the properties of integers:

#include <stdint.h>
...
char *malloced = malloc(10);
uintptr_t ptr_copy = (uintptr_t) malloced;
...
free(malloced);
// it is valid to use ptr_copy here to display what the address was.
printf("The pointer was at: %" PRIxPTR "\n", ptr_copy);

(*) The C11 standard distinguishes automatic variables the address of which is not taken (“that could have been declared with the register storage class”), but Clang does not care.

To answer specifically your suggestion of using memcpy, note that memcpy of indeterminate memory produces indeterminate memory.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281