Passing structs by value

  • Thread starter Christopher Benson-Manica
  • Start date
C

Christopher Benson-Manica

Does the following program exhibit undefined behavior? Specifically,
does passing a struct by value cause undefined behavior if that struct
has as a member a pointer that has been passed to free()?

#include <stdlib.h>

struct stype
{
int *foo;
};

void bar( struct stype foo )
{
}

int main( void )
{
struct stype baz;
baz.foo=malloc( sizeof *baz.foo );
free( baz.foo );
bar( baz ); /* UB or not? */
return 0;
}
 
J

Jack Klein

Does the following program exhibit undefined behavior? Specifically,
does passing a struct by value cause undefined behavior if that struct
has as a member a pointer that has been passed to free()?

I'm not sure why you make a distinction between a pointer in a
structure passed by value, and a bare pointer passed by value. Also
I'm not sure why you make a distinction between a pointer that has an
indeterminate value because it is uninitialized, or because it points
to previously allocated storage that has been freed.
#include <stdlib.h>

struct stype
{
int *foo;
};

void bar( struct stype foo )
{
}

int main( void )
{
struct stype baz;
baz.foo=malloc( sizeof *baz.foo );
free( baz.foo );
bar( baz ); /* UB or not? */
return 0;
}

In several places, the C standard refers to function call arguments
being stored into the function's parameters by assignment. So passing
anything to a function in C is essentially the same as assigning it to
the function's local parameter value.

Assignment in C is always based on value, so passing anything with
indeterminate value, other than an unsigned char, to a function is
undefined behavior, as is any other use of the value of such an
object. It makes no difference whether the object with indeterminate
value is part of a structure, nor does it make any difference how the
value became indeterminate.
 
K

Keith Thompson

Jack Klein said:
I'm not sure why you make a distinction between a pointer in a
structure passed by value, and a bare pointer passed by value. Also
I'm not sure why you make a distinction between a pointer that has an
indeterminate value because it is uninitialized, or because it points
to previously allocated storage that has been freed.


In several places, the C standard refers to function call arguments
being stored into the function's parameters by assignment. So passing
anything to a function in C is essentially the same as assigning it to
the function's local parameter value.

Assignment in C is always based on value, so passing anything with
indeterminate value, other than an unsigned char, to a function is
undefined behavior, as is any other use of the value of such an
object. It makes no difference whether the object with indeterminate
value is part of a structure, nor does it make any difference how the
value became indeterminate.

But DR #222 <http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_222.htm>
says:

The value of a struct or union object is never a trap
representation, even though the value of a member of a struct or
union object may be a trap representation.

This was published in TC2 and in N1124.
 
K

Keith Thompson

pete said:
I don't think that the undefined behavior
associated with accessing freed pointer values
has anything to do with traps.

A freed pointer has an indeterminate value, defined as "either an
unspecified value or a trap representation". (The term "trap
representation" doesn't necessarily imply a trap.)
 
P

pete

Keith said:
A freed pointer has an indeterminate value, defined as "either an
unspecified value or a trap representation". (The term "trap
representation" doesn't necessarily imply a trap.)

I don't think trap representations
have anything to do with freed pointers.

I think we're dealing with the case of
accessing a pointer with unspecified value,
and that access being undefined.

"An unspecified value cannot be a trap representation."

"The value of a pointer becomes indeterminate when
the object it points to reaches the end of its lifetime."

5
Certain object representations need not represent
a value of the object type. If the stored
value of an object has such a representation and is
read by an lvalue expression that does not have character type,
the behavior is undefined. If such a representation is produced
by a side effect that modifies all or any part of the object by an
lvalue expression that does not have character type, the behavior is
undefined. Such a representation is called a trap representation.
 
R

Richard Bos

Keith Thompson said:
But DR #222 <http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_222.htm>
says:

The value of a struct or union object is never a trap
representation, even though the value of a member of a struct or
union object may be a trap representation.

That's weird. I would've thought this would only make sense for unions,
not structs.
In a union, you can easily have the situation that a non-trap value in
one member means that another member does have a trap value (e.g., with
an int and a float: bytes that represent a valid int can be a non-valid
float). This would make passing a union safely impossible in the general
case; one could not guarantee that any non-trap value assigned to any
member of a union would leave the whole union in a passable, non-trap
state.
For a struct, this is much simpler: all members must be valid for it to
be a valid object. Assigning a non-trap value to a single struct member
is already guaranteed not to assign a trap value to any other member of
the struct, unlike in unions.

Richard
 
K

Keith Thompson

pete said:
I don't think trap representations
have anything to do with freed pointers.

I think we're dealing with the case of
accessing a pointer with unspecified value,
and that access being undefined.

The standard doesn't say the value is unspecified; it says it's
indeterminate.
"An unspecified value cannot be a trap representation."

Right, so the two cases are mutually exclusive (presumably at the whim
of the implementation).
"The value of a pointer becomes indeterminate when
the object it points to reaches the end of its lifetime."

Right, meaning it's *either* unspecified *or* a trap representation.
The standard could have been more specific, requiring it to be a trap
representation, but the same bit pattern (address) could later be
returned by another call to malloc(), and a program could detect this
using memcmp().

So:

int *ptr = malloc(sizeof *ptr); /* assume ptr != NULL */
free(ptr);
/*
* ptr now has an indeterminate value, possibly a trap representation
*/
ptr; /* undefined behavior */

The tricky thing is that the value can be unspecified rather than a
trap representation. For example, this program *might* examine the
indeterminate value of ptr1 without invoking undefined behavior:

#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
int main(void)
{
void *ptr1;
void *ptr2;

ptr1 = malloc(32);
assert(ptr1 != NULL);
free(ptr1);

ptr2 = malloc(32);
assert(ptr2 != NULL);

if (memcmp(&ptr1, &ptr2, sizeof ptr1) == 0) {
printf("The same address was re-used\n");
printf("ptr1 = %p\n", ptr1);
}
else {
printf("The address was not re-used\n");
printf("Examining ptr1 may invoke undefined behavior\n");
}

return 0;
}

But if we drop the second malloc() call, after free(ptr1) the object
ptr1 *could* hold a trap representation. (The set of representations
that are trap representations can vary over time during the execution
of the program.)
 
J

Jordan Abel

How do you know that?

Yeah - that is one of the more controversial of the "DS9K claims" - IMO
up there with the "padding bits of DOOM" one elsethread.
 
K

Keith Thompson

pete said:
How do you know that?

Because an implementation on which they can vary can be conforming.
(I'm not claiming that they'll do so on every implementation.)

For example:

void *ptr = malloc(32);
assert(ptr != NULL);
/*
* ptr has a valid value.
*/
free(ptr);
/*
* ptr may now contain a trap representation, even though the bits
* haven't changed.
*/

How does this violate the standard? If the standard intends that ptr
can't have a trap representation, why does it say the value is
indeterminate rather than unspecified? (The only difference between
indeterminate and unspecified is that the former includes trap
representations.)
 
P

pete

Jordan said:
Yeah -
that is one of the more controversial of the "DS9K claims" - IMO
up there with the "padding bits of DOOM" one elsethread.

I can find a reference for that one.

"Some combinations of padding bits might generate trap
representations, for example, if one padding bit is a parity bit."

But I don't see anything in the standard about trap representations,
which even suggests that trap representations
can change during the execution of a program.

The only reference to trap representations
connected with pointers is"

5 An integer may be converted to any pointer type.
Except as previously specified,
the result is implementation-defined,
might not be correctly aligned,
might not point to an entity of the referenced type,
and might be a trap representation.

I read the commas and the "and" of
"this, that, and the other"
as
"this, and that, and the other"
rather than as
"this, or (that, and the other)"
 
P

pete

Keith said:
free(ptr);
/*
* ptr may now contain a trap representation, even though the bits
* haven't changed.
*/

How does this violate the standard? If the standard intends that ptr
can't have a trap representation, why does it say the value is
indeterminate rather than unspecified?

I don't know.
(The only difference between
indeterminate and unspecified is that the former includes trap
representations.)

You might be right.
I believe they (the comp.std.c crowd and others here)
also say that a machine can trap on a pointer
over running an array,
which could be an example of a trap representation
that can change during the execution of a program.
 
J

Jordan Abel

I can find a reference for that one.

"Some combinations of padding bits might generate trap
representations, for example, if one padding bit is a parity bit."

the "padding bits of doom" claim was that if you read out the
representation as unsigned chars, then copy it into another variable at
a later date, those padding bits might be valid anymore - i.e. the DS9K
might suddenly flip all padding bits to 1 and places with 0s would
suddenly become trap representations.
 
M

Mark McIntyre

But I don't see anything in the standard about trap representations,
which even suggests that trap representations
can change during the execution of a program.

Absence of a mention merely means that the standard places no
requirements on it.
In this case, the definition of trap representation (6.2.6.1p5)
doesn't say that it may /not/ change during execution, so you can't
assume it remains constant.
The only reference to trap representations
connected with pointers is"

5 An integer may be converted to any pointer type.
Except as previously specified,
the result is implementation-defined,
might not be correctly aligned,
might not point to an entity of the referenced type,
and might be a trap representation.

I read the commas and the "and" of
"this, that, and the other"
as
"this, and that, and the other"

I believe it means that at least zero of the conditions might apply to
any instance of such a conversion.
 
A

Alex Fraser

[snip]
5 An integer may be converted to any pointer type.
Except as previously specified,
the result is implementation-defined,
might not be correctly aligned,
might not point to an entity of the referenced type,
and might be a trap representation.

I read the commas and the "and" of
"this, that, and the other"
as
"this, and that, and the other"
rather than as
"this, or (that, and the other)"

I read the above as:

Except as previously specified,
the result is implementation defined,
[the result] might not be correctly aligned,
[the result] might not point to an entity of the referenced type, and
[the result] might be a trap representation.

Alex
 
P

pete

Jordan Abel wrote:
the "padding bits of doom" claim was that if you read out the
representation as unsigned chars,
then copy it into another variable at
a later date, those padding bits might be valid anymore - i.e.
the DS9K
might suddenly flip all padding bits to 1 and places with 0s would
suddenly become trap representations.

The value of the padding bits wouldn't change.
The problem would be if the trap representation changed.
 
T

Tim Rentsch

Keith Thompson said:
But DR #222 <http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_222.htm>
says:

The value of a struct or union object is never a trap
representation, even though the value of a member of a struct or
union object may be a trap representation.

That's weird. I would've thought this would only make sense for unions,
not structs. [...stuff about unions...]

For a struct, this is much simpler: all members must be valid for it to
be a valid object. Assigning a non-trap value to a single struct member
is already guaranteed not to assign a trap value to any other member of
the struct, unlike in unions.

Pshaw. Do you mean to say that for

struct small_stack_of_int {
int values[100];
int first_empty_slot;
};

that all elements of the 'values' array must have been assigned, even
if the relation 'first_empty_slot == 0' is true?

It makes perfect sense for struct objects to be partially valid, with
one field (or sometimes more) indicating which other fields may be
accessed. The pattern is quite common, showing up in various kinds
of buffers all the time.

The rule in the DR, and now in n1124, is IMO the most sensible
expression for how non-completely-valid structs should be
expected to behave.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top