4

Yes, I know perfectly well you should not do that. If we have this code:

int *foo() {
    int a = 42;
    return &a;
}

As most C coders know, this is undefined behavior: Using pointer after free()

int *p = foo();
printf("%d\n", *p);

Just so that future readers don't take this as the truth. Everything below until the question was based on a false assumption from me. It's not UB. It's just bad.

As some C coders know, but less than for above, that this also is UB, even though no dereferencing is done: (Was trying to find a good question about this, but did not find anyone)

int *p = foo();
printf("%p\n", p); // Should be printf("%p\n", (void*) p);
                   // Eric P clarified this in his answer, but this missing cast
                   // is not a part of the primary question

And for the same reason, also this is UB, because what happens, is that the pointer become indeterminate, just like an uninitialized variable. A so called dangling pointer.

int *p = foo();
int *q = p;

And somewhat surprising to some, even this is not ok:

free(ptr);
if(ptr == NULL) // Just as bad as if ptr is not initialized

The question

What I do wonder, is if also this single line invokes UB:

int *p = foo();

Or maybe even this?

foo();

In other words, does p become a dangling pointer, or does it get assigned to a dangling pointer?

I don't know if there's any practical use for this, except deeper understanding for the C language. Well one good use case would be to figure out which refactorings that are urgent and which can wait.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
klutt
  • 30,332
  • 17
  • 55
  • 95
  • 1
    I'll need to look *thoroughly* into to the Standard (or maybe someone else will) but, as far as I can tell, `int *p = foo();` is not UB, and nor is the subsequent `printf("%p\n", p);`. That pointer will have a value (an address) and that address can be printed. Only when you attempt to dereference it does UB kick in. – Adrian Mole Mar 12 '21 at 19:50
  • Related: https://stackoverflow.com/q/51083356/1606345 – David Ranieri Mar 12 '21 at 19:50
  • *The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.* - http://port70.net/~nsz/c/c11/n1570.html#6.2.4 – Eugene Sh. Mar 12 '21 at 19:54
  • So I do not think any of the statements under the "Question" part are UB, but `p` has no practical use that won't cause UB. – Eugene Sh. Mar 12 '21 at 19:55
  • @EugeneSh. I was thinking here, does `p` ever even point to an object? Isn't it `&a` that becomes indeterminate before the assignment? – klutt Mar 12 '21 at 20:21
  • @AdrianMole: No, after the lifetime of an object ends, any pointers to it become invalid, meaning they do not have a well-defined value. They do not necessarily point to an address. An original motivation for this was C implementations that had to use auxiliary data to access objects, so that `free` would not just release the memory for the object but would also free or alter the memory used for the auxiliary data. That data may be needed to use the value of the pointer, even to print it. – Eric Postpischil Mar 12 '21 at 20:22
  • @klutt But assigning indeterminate value to `p` makes it indeterminate by itself? Or you mean it might be UB? – Eugene Sh. Mar 12 '21 at 20:22
  • @EugeneSh. Yes, because `p = foo(); q = p;` would invoke UB. – klutt Mar 12 '21 at 20:23
  • @EricPostpischil OK, that's a nice explanation. Maybe I was thinking of you when I mentioned "someone else?" :-) – Adrian Mole Mar 12 '21 at 20:24
  • 3
    I always trust @EricPostpischil when it comes to questions like this. We have very different personalities and opinions, but he is really the master when it comes to small details in the C language, and his answers are always extremely informative. I love them. – klutt Mar 12 '21 at 20:26
  • @EugeneSh. You could ask it like this. Does `p` *become* a dangling pointer, or does it get *assigned* to a dangling pointer? – klutt Mar 12 '21 at 20:28
  • 1
    @klutt Yeah, I git it. So the question is boiling down to whether it is UB to assign a dangling pointer to another pointer variable. I believe we have had similar discussions few times.. – Eugene Sh. Mar 12 '21 at 20:29
  • @EugeneSh. Hmmm, I'm pretty sure that assigning a dangling pointer to another pointer is UB, so no the question does not boil down to that. – klutt Mar 12 '21 at 20:30
  • @klutt The answer below claims the opposite, if I understand it correctly... – Eugene Sh. Mar 12 '21 at 20:31
  • On whether `AnyType x = y;` makes `x` an indeterminate value if `y` is an indeterminate value, I think the C standard itself is silent on this as far as its overt text is concerned, but there is some history in the C committee’s defect report responses and other statements that it does make `x` indeterminate. That is based on memory of prior Stack Overflow discussions; I would not expect to be able to find it in the defect reports or Stack Overflow comments. – Eric Postpischil Mar 12 '21 at 20:33
  • @EugeneSh. Then, the only thing I can say for certain is that at least one of us does not fully understand the answer. :D – klutt Mar 12 '21 at 20:44
  • Aside from just "UB to return a pointer to local variable?" concern, `int *p = foo(); printf("%p\n", p);` has 2) assignment of indeterminate pointer, 2.5) possible conversion of questionable pointer to `void *` 3) passing questionable pointer to `printf()` and 4) printing a questionable pointer. klutt, Are you looking for UB in any of those steps? – chux - Reinstate Monica Mar 12 '21 at 21:52
  • @chux-ReinstateMonica Well, I don't really have a very specific problem to solve in this case, so I think the best thing is to just let the gods of this site break things down into small details. But I did miss adding the void cast. I know it should be there, but forgot it. The reason that I have not edited it is that it might invalidate Erics answer partially. Do you think it would? – klutt Mar 12 '21 at 22:09
  • When a clarification edit might invalidate a good answer, append it or leave out. – chux - Reinstate Monica Mar 12 '21 at 22:22
  • @chux-ReinstateMonica I did an edit – klutt Mar 12 '21 at 22:40
  • There is no practical use to write `int a; a = 10; a = 20;` but it is not forbidden. – i486 Aug 26 '21 at 10:51
  • @i486 I don't really see how that is relevant – klutt Aug 26 '21 at 11:45
  • @klutt I tried to say that there are too many things that you can write and compile but there is no practical use. – i486 Aug 26 '21 at 12:37

1 Answers1

3

C 2018 6.2.4 says “… The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.” 3.19.2 tells us an indeterminate value is “either an unspecified value or a trap representation.” 3.19.3 tells us an unspecified value is a “valid value of the relevant type where this document imposes no requirements on which value is chosen in any instance” (meaning the value can appear to be different each time we use it, even if no apparent changes are made to it).

Thus in:

int *p = foo();
printf("%p\n", (void *) p); // "(void *)" added to pass correct type for %p.

We do not know what value will be printed for p. If the C implementation has no trap representations for pointers, then its indeterminate value cannot be a trap representation, so there is no undefined behavior. However, it is, per 3.19.3, some valid value.

This answer does not speak to other questions in the post, such as whether:

int *p = foo();
int *q = p;

assigns to q some value that is fixed once the assignment is done (thus printing q repeatedly always prints the same value) or assigns to q the notional “indeterminate value” (thus printing q repeatedly would be allowed to print different values). (Either way, it is not undefined behavior, except for the possibility of trap representations.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • 1
    *"This answer does not speak to other questions in the post,"* - I would REALLY like to know the answer if you know it. But really good answer so far. – klutt Mar 12 '21 at 20:40
  • "If the C implementation has no trap representations for pointers, then its indeterminate value cannot be a trap representation, so there is no undefined behavior." --> So if the implementation does has a pointer trap value, it is UB to do the assignment? – chux - Reinstate Monica Mar 12 '21 at 21:40
  • Would it be trap representation to have pointer that points to an address misaligned for the type? – Christian Gibbons Mar 12 '21 at 22:18
  • @chux-ReinstateMonica: If the implementation has a trap representation **and** the value of the pointer is a trap representation, then the behavior is not defined by the C standard. – Eric Postpischil Mar 12 '21 at 22:40
  • @ChristianGibbons: Possibly, but one might have to interpret the wording in 6.2.8 that regarding alignment restricting “allocation” of an object to multiples of the alignment requirement as meaning that unaligned pointers are not values of the trap (and so are trap representations, although they need not trap, as the behavior is not defined), and that is not really the point of the question here, so I do not think we should get into it. A separate question could be entered. – Eric Postpischil Mar 12 '21 at 22:43
  • @EricPostpischil Ouch. It sounds really meta if the same code *sometimes* is UB – klutt Mar 12 '21 at 23:24
  • @klutt: The authors of the C Standard figured that if they couldn't see any reason why anyone would care if a particular action invoked UB, there was no reason to expend ink on the issue. This is especially true in cases where the Standard would specify the behavior of an action, but also classify an overlapping category of actions as UB. Since implementations were expected to give priority to the former except when there was a good reason for doing otherwise, the only time it would matter if an action was both specified and UB would be if there would be some reason for an implementation... – supercat Oct 07 '21 at 20:46
  • ...not to process it as specified. If you want something really meta, read the definition of `based upon` in the so-called "Formal definition of restrict" (search for N1570 and look in there for section 6.7.3.1) , and determine whether the pointer expression `p+0` within the code `int x[10]; int test(int *restrict p) { *p = 1; if (p==x) *(p+0)=2; return *p; }` is based upon `restrict p`. – supercat Oct 07 '21 at 20:52
  • Just for comparison, the governing rule in C++ is https://eel.is/c++draft/basic.stc#general-4 which uses the concept of an *invalid pointer value*. It's implementation-defined whether such a value can be passed to `printf`, copied to another pointer variable, compared to `nullptr`, etc. – Ben Voigt Oct 07 '21 at 21:51