Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on Is it UB to read value of heap pointer after freeing?

Parent

Is it UB to read value of heap pointer after freeing?

+3
−0

I've read in a guide that "the use of indeterminate memory for anything, including apparently harmless comparison or arithmetic, can have undefined behavior if the value can be a trap representation for the type."

The included quote from C11, section 6.2.4 §2, says:

The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime.

The reason I'm mentioning the quote here is partially because I don't have a copy of the C standard.

Is reading the value of a pointer returned by a heap allocation function after its deallocation truly an instance of undefined behavior? (For example, debuggers may read and output the address to various I/O destinations: printer, screen, disk, network, etc.)

Consider the following program:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  void *pointer = malloc(1);
  if (pointer == NULL) return 1;
  free(pointer);
  printf("%p\n", pointer); /* Reading the address. Undefined behavior? */
  return 0;
}

Does the specification for this differ across ANSI C and other versions of ISO C?

History

1 comment thread

Very similar question here (1 comment)
Post
+1
−0

It may or may not be undefined behavior:

  • Depending on if the ABI of the given target system has trap representations for pointers and the value we ended up now happens to be a trap representation.
  • Otherwise it is unspecified behavior and will work fine, but print garbage.

(C23 renamed trap values "non-value representations".)

Be aware that there is a lot of misinformation and confusion over this out on the Internet, so you must dismiss anyone who cannot give you direct quotes from the C standard. There are lots of people subjectively claiming "it is undefined behavior because I say so"...


This is a recurring "language lawyer" question.

Contrary to popular belief, it is not UB to read an indeterminate value, it is unspecified behavior. With some special exceptions, details and C standard sources are given here.

The pointer variable was initialized so it is not indeterminate for that reason. Thus it doesn't matter that it has automatic storage duration or if its address was taken etc. It became indeterminate after the free call.

The pointer value in itself may be a trap representation for that given pointer type, in which case the program invokes undefined behavior. (Which is highly unlikely in practice since nothing happened that changed the pointer lvalue itself.)

Otherwise, the pointer becomes an indeterminate value which is unspecified. These kind of "wobbly values" have been up for debate a couple of times in the C committee and the conclusion were pretty much that the value need not be consistent if read several times, or the compiler might give the same garbage value every time - we don't know and we can't assume anything about it.

Unfortunately, some bad compilers chose to be so-called "low quality implementations" in this case. That is, the compiler chose to crash and break programs needlessly when it doesn't have to. Example: clang 15 miscompiles code accessing indeterminate values. From clang 15 for x86, the compiler chose to become bats*** crazy and just cut program generation halfways, giving the programmer a half program to execute with seg fault as guaranteed outcome, which is obviously useless. That compiler remains just as broken as of today.

However, the specific example of the question (which is far less nasty than the one I cooked up in the SO post) actually runs even in clang v22 x86, printing a garbage value. x86_64 does at least to my knowledge not have any trap representations for void* or other object pointers.


EDIT

To clarify to everyone why accessing the pointer variable itself while it is indeterminate cannot reasonably be UB, lets consider this:

int* p=NULL; 
{
  int var=1; 
  if(something) 
    p=&var; 
} 
if(p == NULL)
  ....

After the inner scope ends, p might point to a variable no longer existing - the value of p is indeterminate. Now if this was UB, then we couldn't do the final if(p == NULL) check or the program might crash on that line. That's not sensible, it would break the whole language.

Even more obvious:

int* p = malloc(n);
free(p);
p = NULL;

If using p while the value is indeterminate, you wouldn't even be able to write bog standard code like the above, because the assignment involves a value computation of the left operand p before it is written to (C23 6.5.17.1).

Summary: if accessing the pointer while it's value is indeterminate, that pointer variable would turn into some kind of explosive mine that might crash the program if we ever try to reuse it again. We would never be able to verify or re-use pointer variables.

History

1 comment thread

`=` doesn't evaluate (edit/correction: use the value) of the left operand. (13 comments)
`=` doesn't evaluate (edit/correction: use the value) of the left operand.
alx‭ wrote 11 days ago

Even more obvious:

int* p = malloc(n);
free(p);
p = NULL;

If using p while the value is indeterminate, you wouldn't even be able to write bog standard code like the above, because the assignment involves a value computation of the left operand p before it is written to (C23 6.5.17.1).

That is not correct. The = operator does not read the value of the left operand. That wouldn't make sense.

The text I guess has confused you is The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. from 6.5.17.1p3. However, that text should be interpreted as covering the compound assignment operators, and not requiring an evaluation of the left operand (that should be specified in 6.5.17.2).

I agree the wording may be a bit confusing, but your interpretation is unreasonable.

Lundin‭ wrote 11 days ago · edited 11 days ago

alx‭ No not that text. C23 6.5.17.1 "The evaluations of the operands are unsequenced". There was a recent question on SO regarding if the left operand of = is evaluated and my answer here explains why. If it isn't enough that the standard explicitly says that the operands are evaluated. It doesn't matter if it is simple or compound assignment, as my SO answer illustrates.

alx‭ wrote 8 days ago · edited 8 days ago

You're right: the left operand is evaluated. However, that evaluation is only performed for determining the designated object. The value stored in the designated object is not read; that would require lvalue conversion, which is inhibited for the left operand of the simple assignment operator (C23 6.3.2.1p2).

Weirdly, that wording seems to inhibit lvalue conversion also for the compound assignment operators, but the semantics of the compound assignment (C23 6.5.17.3p4) seem to imply that lvalue conversion is performed at least for the implied use of the lvalue in the right-hand side.

Lundin‭ wrote 8 days ago

alx‭ 6.5.17.1 "The type of an assignment expression is the type the left operand would have after lvalue conversion." This is a bit ambiguous, but the rest of that paragraph is not: "The evaluations of the operands are unsequenced." So the left operand is evaluated. Constraints already states that the left operand must be a modifiable lvalue so it need not undergo lvalue conversion for that reason. None of this is particularly relevant in regards of evaluation, which is concerned about value computations and side effects. The definition of evaluation in 5.2.2.4: "Evaluation of an expression in general includes both value computations and initiation of side effects. Value computation for an lvalue expression includes determining the identity of the designated object." In this case the left operand needs to be evaluated for side effects.

Lundin‭ wrote 8 days ago · edited 8 days ago

To illustrate:

#include <stdio.h>

int x;

int* func (void)
{
  puts("evaluated");
  return &x;
}

int main()
{
  *func() = 1;
  sizeof(puts("not evaluated"));
}

This code prints "evaluated" but it does not print "not evaluated".

alx‭ wrote 8 days ago

Lundin‭

C23 5.1.2.4p2 indeed says that Evaluation of an expression in general includes both value computations and initiation of side effects., but I think that in general is meant to be overridden by specific rules.

Indeed, as you say, the left operand of = has to be evaluated for its side effects, but that evaluation includes designation of the object and initiation of side effects, but notably doesn't include lvalue conversion --it is inhibited by C23 6.3.2.1p2--. Lvalue conversion is what would read the value stored in the object designated by the left expression: an lvalue that does not have array type is converted to the value stored in the designated object.

In fact, while lvalue conversion of the left operand does not happen as part of the evaluation, subexpressions within the left operand are also evaluated, and those indeed have lvalue conversions performed as necessary, which is what allows reading intermediate values.

alx‭ wrote 8 days ago

For example, in *(p+1) = 0, the whole *(p+1) is an lvalue expression, and is evaluated for designating an object and for initiating the side effect of writing a 0 to it, but its stored value is not read (lvalue conversion is not performed on the whole).

But p is a subexpression of it, and is an lvalue expression on which lvalue conversion is performed (and thus, its stored value is read, and then it's not an lvalue anymore).

alx‭ wrote 8 days ago · edited 8 days ago

That is, "evaluation" isn't a black-or-white thing. It's not "everything happens" or "nothing happens". It's more like in general, evaluation means several things, but in some cases, only one or a few of these things actually happen. Also, the effects of evaluation on the expression might be different from the effects of the evaluation on subexpressions of it.

Lundin‭ wrote 8 days ago

alx‭ I don't get why you are so hung up on lvalue conversion if you now agree that the operand must be evaluated for side effects. The question was never if the left operand undergoes lvalue conversion or not. You started this whole comment thread with "= doesn't evaluate the left operand".

alx‭ wrote 8 days ago · edited 8 days ago

Lundin‭

Yes, I was wrong on the first comment and the title. That I admitted above.

The reason why I'm insisting on lvalue conversion is this reasoning in your post:

Even more obvious:

int* p = malloc(n);
free(p);
p = NULL;

If using p while the value is indeterminate, you wouldn't even be able to write bog standard code like the above, because the assignment involves a value computation of the left operand p before it is written to (C23 6.5.17.1).

You're saying that if using p while the value is indeterminate was UB, this fine code would break. However, I argue that some uses of p don't result in using the indeterminate value contained in the object designated by p.

In the example quoted, p = NULL uses p only to designate an object, but not to read its contained value, and thus, while we use (and evaluate) p, we never use the contained indeterminate value. That's what makes this example not trigger the potential UB.

...

alx‭ wrote 8 days ago

...

And thus, this is not an appropriate example for discussing whether using an indeterminate value results in UB (potentially or definitely), because this example is not using an indeterminate value at all.

Here I'm not arguing whether reading the indeterminate value results in UB or not. Just saying that this example is not appropriate for discussing that.

System‭ wrote 8 days ago

Thread renamed from "= doesn't evaluate the left operand." to "= doesn't evaluate (edit: use the value) of the left operand." by alx‭

System‭ wrote 8 days ago

Thread renamed from "= doesn't evaluate (edit: use the value) of the left operand." to "= doesn't evaluate (edit/correction: use the value) of the left operand." by alx‭