Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Indeterminate Values and Undefined Behaviour - anyone know?

3 views
Skip to first unread message

Roger Onslow

unread,
Nov 19, 1997, 3:00:00 AM11/19/97
to

There appears to be some confusion (or at least discussion on
interpretation) about what constitutes 'undefined behaviour' in relation to
'indeterminate values' according to the standard. Mostly this has been in
relation to what happens to a pointer after 'free' or 'fclose'.

The standard says that undefined behaviour includes the use of an object
with indeterminate value (Note - it say use of the object, NOT use of the
indeterminate value)

I'd like to see if there is a consensus on ALL (or any :-) of the following
examples.

For all these, assume we have..
int* p;
and the value of p is currently indeterminate (either not initialised yet,
or just after a call to 'free' - whatever).

Which of the following expressions would officially (by the standard) result
in 'undefined behaviour'. This does NOT mean which ones actually work in
some particular implementation.
p
*p
&p
*&p
p == NULL
p = NULL
(unsigned char*)p
*(unsigned char*)p
(unsigned char*)&p
*(unsigned char*)p
sizeof(p)
sizeof(*p)
memcpy(q, p, sizeof(int)) /* where q is an int* as well */
memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

One way of interpreting that part of the standard would say that ALL of
these are undefined because they 'use' an objet of indeterminate value - but
that cannot be right, because then you could never assign a value. Does
'use' mean "use as an r-value"?

I hope to hear some interesting and informed replies to this.

Roger


Tom Payne

unread,
Nov 19, 1997, 3:00:00 AM11/19/97
to

In comp.std.c Roger Onslow <Roger_Ons...@compsys.com.au> wrote:

[...]
: For all these, assume we have..


: int* p;
: and the value of p is currently indeterminate (either not initialised yet,
: or just after a call to 'free' - whatever).

: Which of the following expressions would officially (by the standard) result
: in 'undefined behaviour'.

[...]
: p


: *p
: &p
: *&p
: p == NULL
: p = NULL
: (unsigned char*)p
: *(unsigned char*)p
: (unsigned char*)&p
: *(unsigned char*)p
: sizeof(p)
: sizeof(*p)
: memcpy(q, p, sizeof(int)) /* where q is an int* as well */
: memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

: One way of interpreting that part of the standard would say that ALL of
: these are undefined because they 'use' an objet of indeterminate value - but
: that cannot be right, because then you could never assign a value. Does
: 'use' mean "use as an r-value"?

As I understand it, the standard was designed to accomodate
architectures that trap on read access to certain pointer registers
when they hold invalid values. So the implicit rule is that
evaluating any expression that reads p (i.e., uses p as an rvalue)
yields undefined behavior:

* p can be assigned any appropriate value, including NULL -- that
doesn't involve reading p.

* &p can be used anywhere appropriate -- it doesn't invove reading p.

* sizeof() is always harmless -- it doesn't evaluate its argument.

* casting is an operation on rvalues and not lvalues -- casting p
requires reading p.

* memcpy(&q, &p, sizeof(int*)) should yield undefined behavior
-- it involves reading p (and possibly copying its value from a
sensitive register to its memory location).

* the rest yield undefined behavior -- they visibly require reading p.

Tom Payne

Clive D.W. Feather

unread,
Nov 23, 1997, 3:00:00 AM11/23/97
to

In article <kImcQdJ...@geraldo.newaygo.mi.us>, Roger Onslow
<Roger_Ons...@compsys.com.au> writes

>For all these, assume we have..
> int* p;
>and the value of p is currently indeterminate (either not initialised yet,
>or just after a call to 'free' - whatever).
>
>Which of the following expressions would officially (by the standard) result
>in 'undefined behaviour'. This does NOT mean which ones actually work in
>some particular implementation.

I'm not going to address the list per se. There are four contexts in
which a variable can be used:
(1) value is read, such as "pp = p" or "(void *) p";
(2) value is written, such as "p = NULL";
(3) value is read and written, such as "p += 1";
(4) value is ignored, such as "(void) p" or "sizeof p".

Cases (1) and (3) are undefined behaviour. Cases (2) and (4) are not.

> memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

This is defined in C9X to work on a byte-by-byte basis. The
indeterminate value is copied in a strictly conforming manner. At least,
that's how I interpret the words I wrote.

--
Clive D.W. Feather | Director of Software Development | Home email:
Tel: +44 181 371 1138 | Demon Internet Ltd. | <cl...@davros.org>
Fax: +44 181 371 1037 | <cl...@demon.net> |
Written on my laptop; please observe the Reply-To address |

Nick Maclaren

unread,
Nov 23, 1997, 3:00:00 AM11/23/97
to

In article <cJjli4A0...@on-the-train.demon.co.uk>,

Clive D.W. Feather <cl...@demon.net> wrote:
>In article <kImcQdJ...@geraldo.newaygo.mi.us>, Roger Onslow
><Roger_Ons...@compsys.com.au> writes
>>For all these, assume we have..
>> int* p;
>>and the value of p is currently indeterminate (either not initialised yet,
>>or just after a call to 'free' - whatever).
>>
>>Which of the following expressions would officially (by the standard) result
>>in 'undefined behaviour'. This does NOT mean which ones actually work in
>>some particular implementation.
>
>I'm not going to address the list per se. There are four contexts in
>which a variable can be used:
>(1) value is read, such as "pp = p" or "(void *) p";
>(2) value is written, such as "p = NULL";
>(3) value is read and written, such as "p += 1";
>(4) value is ignored, such as "(void) p" or "sizeof p".
>
>Cases (1) and (3) are undefined behaviour. Cases (2) and (4) are not.

Well, I cannot find any such statement in the current standard. In
particular, the descriptions of both equality operators and assignment
refer to 'pointers to object TYPES'. I quite agree with your
interpretation for uninitialised variables, pointer incrementation and
relational operators, but let's leave them out of it.

Your statement above is equivalent to saying that a pointer to an
object type is defined only if the object has current existence, which
is reasonable but not stated anywhere that I can find. And, if it IS
the case, why a pointer to an object type - why not just a pointer to
an object?

The cases that I am thinking of are where the pointer value has been
valid in the past (or will be in the future - though I cannot think of
an example), such as after a file has been closed or store freed.


Nick Maclaren,
University of Cambridge Computer Laboratory,
New Museums Site, Pembroke Street, Cambridge CB2 3QG, England.
Email: nm...@cam.ac.uk
Tel.: +44 1223 334761 Fax: +44 1223 334679

Lawrence Kirby

unread,
Nov 23, 1997, 3:00:00 AM11/23/97
to

In article <cJjli4A0...@on-the-train.demon.co.uk>

cl...@demon.net "Clive D.W. Feather" writes:

>I'm not going to address the list per se. There are four contexts in
>which a variable can be used:
>(1) value is read, such as "pp = p" or "(void *) p";
>(2) value is written, such as "p = NULL";
>(3) value is read and written, such as "p += 1";
>(4) value is ignored, such as "(void) p" or "sizeof p".
>
>Cases (1) and (3) are undefined behaviour. Cases (2) and (4) are not.

It seems to me that (void)p results in undefined behaviour. Casts are
operators that take a value as an operand and do something with it. The
fact that you can't do anything with the result of the cast doesn't
change that.

>> memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */
>
>This is defined in C9X to work on a byte-by-byte basis. The
>indeterminate value is copied in a strictly conforming manner. At least,
>that's how I interpret the words I wrote.

What about:

for (i = 0; i < sizeof p; i++)
((unsigned char *)&q)[i] = ((unsigned char *)&p)[i];

Does it make any difference whether p is an uninitialised pointer or a
pointer to freed memory?

--
-----------------------------------------
Lawrence Kirby | fr...@genesis.demon.co.uk
Wilts, England | 7073...@compuserve.com
-----------------------------------------


Lawrence Kirby

unread,
Nov 23, 1997, 3:00:00 AM11/23/97
to

In article <65a05o$iju$1...@lyra.csx.cam.ac.uk>
nm...@cus.cam.ac.uk "Nick Maclaren" writes:

>In article <cJjli4A0...@on-the-train.demon.co.uk>,
>Clive D.W. Feather <cl...@demon.net> wrote:
>>In article <kImcQdJ...@geraldo.newaygo.mi.us>, Roger Onslow
>><Roger_Ons...@compsys.com.au> writes
>>>For all these, assume we have..
>>> int* p;
>>>and the value of p is currently indeterminate (either not initialised yet,
>>>or just after a call to 'free' - whatever).
>>>
>>>Which of the following expressions would officially (by the standard) result
>>>in 'undefined behaviour'. This does NOT mean which ones actually work in
>>>some particular implementation.
>>

>>I'm not going to address the list per se. There are four contexts in
>>which a variable can be used:
>>(1) value is read, such as "pp = p" or "(void *) p";
>>(2) value is written, such as "p = NULL";
>>(3) value is read and written, such as "p += 1";
>>(4) value is ignored, such as "(void) p" or "sizeof p".
>>
>>Cases (1) and (3) are undefined behaviour. Cases (2) and (4) are not.
>

>Well, I cannot find any such statement in the current standard.

Which case are you referring to?

>In
>particular, the descriptions of both equality operators and assignment
>refer to 'pointers to object TYPES'. I quite agree with your
>interpretation for uninitialised variables, pointer incrementation and
>relational operators, but let's leave them out of it.

Maybe what you are looking for is in 3.16 which makes use of indeterminately
valued objects result in undefined behaviour. AFAIK the way the standard
is worded an indeterminate value can only originate from an object.

>Your statement above is equivalent to saying that a pointer to an
>object type is defined only if the object has current existence, which
>is reasonable but not stated anywhere that I can find.

6.1.2.4 says that a pointer to an automatic variable that no longer exists
is indeterminate.

7.10.3 says that a pointer thsat refers to freed space is indeterminate.

7.9.3 says that a pointer to FILE is indeterminate after the file is closed.

Are there any other ways of destroying an object in a stricty conforming
program (and if so are there any not covered by similar statements)? If
not then your statement an be shown to be true.

> And, if it IS
>the case, why a pointer to an object type - why not just a pointer to
>an object?

Well, I suppose there is always the "one past the end of the object"
pointer and of course there is the null pointer. Both of these are valid
pointer values but don't point to an object.

Douglas A. Gwyn

unread,
Nov 24, 1997, 3:00:00 AM11/24/97
to

In article <880328...@genesis.demon.co.uk>,

fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:
>It seems to me that (void)p results in undefined behaviour. Casts are
>operators that take a value as an operand and do something with it.

Indeed, that is part of the semantic specification for cast operators.
We could have made a special case for casting to void, but I don't
think we did.

> for (i = 0; i < sizeof p; i++)
> ((unsigned char *)&q)[i] = ((unsigned char *)&p)[i];
>Does it make any difference whether p is an uninitialised pointer or a
>pointer to freed memory?

Several conditions have to be met for any code example to be usable
in a strictly conforming program. For example, in the above snippet,
q must denote a modifiable, addressable, live (identifier in scope)
object that has a size of at least sizeof p, and similar restrictions
apply to p. However, so long as p is accessible there should be no
problem with accessing it, which is all your question really boils
down to.


Nick Maclaren

unread,
Nov 24, 1997, 3:00:00 AM11/24/97
to

In article <880329...@genesis.demon.co.uk>, fr...@genesis.demon.co.uk (Lawrence Kirby) writes:
|>
|> Maybe what you are looking for is in 3.16 which makes use of indeterminately
|> valued objects result in undefined behaviour. AFAIK the way the standard
|> is worded an indeterminate value can only originate from an object.
|>
|> 6.1.2.4 says that a pointer to an automatic variable that no longer exists
|> is indeterminate.
|>
|> 7.10.3 says that a pointer thsat refers to freed space is indeterminate.
|>
|> 7.9.3 says that a pointer to FILE is indeterminate after the file is closed.

Er, yes. I had missed the first. Thank you. Egg on the face time,
and a humble apology to Clive!

Tom Payne

unread,
Nov 24, 1997, 3:00:00 AM11/24/97
to

In comp.std.c Clive D.W. Feather <cl...@on-the-train.demon.co.uk> wrote:
: In article <kImcQdJ...@geraldo.newaygo.mi.us>, Roger Onslow

: > memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

: This is defined in C9X to work on a byte-by-byte basis. The
: indeterminate value is copied in a strictly conforming manner. At least,
: that's how I interpret the words I wrote.

Is it required that the bytes thus copied be those of the
representation of the most current value of p? If so, allowing this
invocation of memcpy effectively precludes storing p in a special
pointer register that can trap on read access when p dangles. (It was
my understanding that the ability to use such registers is important
to certain vendors.)

Consider, for example:

int* r = p;
free(r);
memcpy(&q, &p, sizeof(int*));

which could trap unless p gets copied back from the special register
before r is freed. (Copying p whenever a potential alias is about to
be freed would be unacceptable.)

Tom Payne

Richard A. O'Keefe

unread,
Nov 25, 1997, 3:00:00 AM11/25/97
to

Tom Payne <t...@cs.ucr.edu> writes:
>: > memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

>Is it required that the bytes thus copied be those of the


>representation of the most current value of p? If so, allowing this
>invocation of memcpy effectively precludes storing p in a special
>pointer register that can trap on read access when p dangles.

It isn't clear what "the bytes of the representation of the most current
value of" a variable _means_. Consider for example an Alpha, where a
floating point value may be stored in memory in VAX format, but the
instruction that loads it into a register for calculations will convert
it to an IEEE format. Which is "THE representation"? BOTH representations
are current. The form in the register can be stored back either in VAX
format or in IEEE format.

Nor is it clear why this would prevent normal operations on a variable
(such as p == NULL) using a register. Any variable may have *multiple*
*current* representations, some in registers, some in memory, not necessarily
all in the same format, at the same instant. Which is "THE representation"?
Surely the point is that a compiler may freely pick ANY of them, however
inconvenient it may be for the programmer, as long as conformant code
can't tell the difference.

(It was
>my understanding that the ability to use such registers is important
>to certain vendors.)

>Consider, for example:

> int* r = p;
> free(r);
> memcpy(&q, &p, sizeof(int*));

>which could trap unless p gets copied back from the special register
>before r is freed. (Copying p whenever a potential alias is about to
>be freed would be unacceptable.)

I don't understand this. The bits in memory for p wouldn't change;
the bits in the register for p wouldn't change. The reason why this
kind of thing is undefined is _so that_ the compiler _doesn't_ have
to reconcile things at points like this, is it not?

--
John Æneas Byron O'Keefe; 1921/02/04-1997/09/27; TLG,TLTA,BBTNOTL.
Richard A. O'Keefe; RMIT Comp.Sci; http://www.cs.rmit.edu.au/%7Eok

Douglas A. Gwyn

unread,
Nov 25, 1997, 3:00:00 AM11/25/97
to

In article <65cnml$j69$1...@skylark.ucr.edu>,

Tom Payne <t...@cs.ucr.edu> wrote:
>: > memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */
>Is it required that the bytes thus copied be those of the
>representation of the most current value of p?

The value most recently stored according to the rules for
sequence points.

>If so, allowing this invocation of memcpy effectively precludes storing
>p in a special pointer register that can trap on read access when p

>dangles. (It was my understanding that the ability to use such


>registers is important to certain vendors.)

No, in the example p denotes an addressable "live" object
which contains at least sizeof(int*) bytes, and which has
to follow the general C rules for objects.

A conforming implementation may cache values in registers,
and pointer operations may use different kinds of
registers than for integer operations, but none of that
has anything to do with the issue at hand.

I think the previous discussions about invalidation of pointer
values may have confused you. Not everything said in those
discussions was correct or relevant.


Tom Payne

unread,
Nov 25, 1997, 3:00:00 AM11/25/97
to

In comp.std.c Richard A. O'Keefe <o...@goanna.cs.rmit.edu.au> wrote:
: Tom Payne <t...@cs.ucr.edu> writes:
: >: > memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */

: >Is it required that the bytes thus copied be those of the

: >representation of the most current value of p? If so, allowing this


: >invocation of memcpy effectively precludes storing p in a special
: >pointer register that can trap on read access when p dangles.

: It isn't clear what "the bytes of the representation of the most current
: value of" a variable _means_. Consider for example an Alpha, where a


: floating point value may be stored in memory in VAX format, but the
: instruction that loads it into a register for calculations will convert
: it to an IEEE format. Which is "THE representation"? BOTH representations
: are current. The form in the register can be stored back either in VAX
: format or in IEEE format.

Good point. The choice of representation schemes is implementation
dependent and, as you point out, not necessarily unique. My question,
however, involves the currency of the representation that gets copied
from &p, in whatever representation scheme the implementation chooses
for that location.

: Nor is it clear why this would prevent normal operations on a variable


: (such as p == NULL) using a register. Any variable may have *multiple*
: *current* representations, some in registers, some in memory, not necessarily
: all in the same format, at the same instant. Which is "THE representation"?
: Surely the point is that a compiler may freely pick ANY of them, however
: inconvenient it may be for the programmer, as long as conformant code
: can't tell the difference.

Right. The question, however, concerns whether the bytes thus copied
have to represent such a *current* value.

: (It was


: >my understanding that the ability to use such registers is important
: >to certain vendors.)

: >Consider, for example:

: > int* r = p;
: > free(r);
: > memcpy(&q, &p, sizeof(int*));

: >which could trap unless p gets copied back from the special register
: >before r is freed. (Copying p whenever a potential alias is about to
: >be freed would be unacceptable.)

: I don't understand this. The bits in memory for p wouldn't change;
: the bits in the register for p wouldn't change.

But they might not represent the same value -- the bits in memory
might represent a stale value. If we are allowed to copy whatever
bits are found at &p, there's no problem. If, however, the bits thus
copied must represent the current value, then we need to store the
register value into memory, which can provoke a trap on certain
implementations.

: The reason why this


: kind of thing is undefined is _so that_ the compiler _doesn't_ have
: to reconcile things at points like this, is it not?

That's *exactly* my concern here. Per Clive's posting, to which I was
responding:

This is defined in C9X to work on a byte-by-byte basis. The
indeterminate value is copied in a strictly conforming manner.
At least, that's how I interpret the words I wrote.

Specifically, my question is: does the compiler have to reconcile
things at this point? If so, what about the fact that such
reconciliation might provoke a trap in implementations that use
pointers registers that (sometimes) trap upon being read when their
content is invalid?

Tom Payne

Lawrence Kirby

unread,
Nov 26, 1997, 3:00:00 AM11/26/97
to

In article <65aq53$n...@sjx-ixn9.ix.netcom.com>
gw...@ix.netcom.com "Douglas A. Gwyn" writes:

...

>Indeed, that is part of the semantic specification for cast operators.
>We could have made a special case for casting to void, but I don't
>think we did.

So to be clear does that mean that:

int x;

(void)x;

results in undefined behaviour? what about:


int foo(void)
{
return;
}

int main(void)
{
foo();

(void)foo();

return 0;
}

It is generally accepted that foo(); doesn't "use" the return value of
foo(), but what about (void)foo(); ? If what you say above is correct then
this does use the value and results in undefined behaviour, since a cast
does evaluate its operand. Also consider:

int x;

x;

Is x evaluated here? On reflection I don't think so since no operator is
applied to it to take its value. I see nothing to make is less defined than:

&x;

or perhaps closer still:

sizeof x;

>> for (i = 0; i < sizeof p; i++)
>> ((unsigned char *)&q)[i] = ((unsigned char *)&p)[i];
>>Does it make any difference whether p is an uninitialised pointer or a
>>pointer to freed memory?
>
>Several conditions have to be met for any code example to be usable
>in a strictly conforming program. For example, in the above snippet,
>q must denote a modifiable, addressable, live (identifier in scope)
>object that has a size of at least sizeof p, and similar restrictions
>apply to p. However, so long as p is accessible there should be no
>problem with accessing it, which is all your question really boils
>down to.

What if p is an uninitialised automatic variable? According to 6.5.7 the
value of such an object is indeterminate. Consider the first byte
of p i.e. the object designated by *(unsigned char *)&p. That is an
automatic object that is uninitialised so according to 6.5.7 that too
has an indeterminate value so it may cause the loop above (and hence
memcpy too) to fail. There are simpler examples:

unsigned char x, y=x;

unsigned char a, b;

memcpy(&a, &b, 1);

How far does this magic property of unsigned char extend? Is there anything
preventing an implementation only making an object (or more specifically the
bytes that make up an object) readable for any purpose after it is first
written to/initialised? This doesn't necessarily need fancy hardware support,
simple dataflow analysis in the compiler can detect most violations of this.

Peter Seebach

unread,
Nov 26, 1997, 3:00:00 AM11/26/97
to

In article <880566...@genesis.demon.co.uk>,

Lawrence Kirby <fr...@genesis.demon.co.uk> wrote:
> int x;

> x;

>Is x evaluated here? On reflection I don't think so since no operator is
>applied to it to take its value. I see nothing to make is less defined than:

I think it is. ; evaluates the expression for side-effects, no?

-s
--
se...@plethora.net -- I am not speaking for my employer. Copyright '97
All rights reserved. This was not sent by my cat. C and Unix wizard -
send mail for help, or send money for a consultation. Visit my new ISP
<URL:http://www.plethora.net/> --- More Net, Less Spam! Plethora . Net

Lawrence Kirby

unread,
Nov 26, 1997, 3:00:00 AM11/26/97
to

In article <65hrid$ss$1...@darla.visi.com>
se...@plethora.net "Peter Seebach" writes:

>In article <880566...@genesis.demon.co.uk>,
>Lawrence Kirby <fr...@genesis.demon.co.uk> wrote:
>> int x;
>
>> x;
>
>>Is x evaluated here? On reflection I don't think so since no operator is
>>applied to it to take its value. I see nothing to make is less defined than:
>
>I think it is. ; evaluates the expression for side-effects, no?

Yes, that is certainly true. However & also evaluates its operand for
side-effects but it doesn't take its value. OK, I'll reverse one thing
I wrote: & is a better example than sizeof.

{
int array[2];
int *ptr;

ptr = array;
&*++ptr; /* Not undefined */

ptr = array;
*++ptr; /* ? */

ptr = array;
(void)*++ptr; /* Undefined ? */

Peter Seebach

unread,
Nov 26, 1997, 3:00:00 AM11/26/97
to

In article <880582...@genesis.demon.co.uk>,

Lawrence Kirby <fr...@genesis.demon.co.uk> wrote:
>Yes, that is certainly true. However & also evaluates its operand for
>side-effects but it doesn't take its value. OK, I'll reverse one thing
>I wrote: & is a better example than sizeof.

Hmm. Point...

>{
> int array[2];
> int *ptr;

> ptr = array;
> &*++ptr; /* Not undefined */

> ptr = array;
> *++ptr; /* ? */

I say undefined, because the value of the expression is the indeterminate
thing. Note that in C9X, we actually have wording to explicitly cover the
intent that '&*' and '*&' are both semantically null, and that you really
aren't using the value of a thing you take the address of, and this happens
because & is suppressiing part of the evaluation.

However, my understanding is that, when asked, the committee has said that
this is the intended meaning of the current standard.

> ptr = array;
> (void)*++ptr; /* Undefined ? */

I say it is.

Douglas A. Gwyn

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

In article <880566...@genesis.demon.co.uk>,
fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:

>So to be clear does that mean that:
> int x;
> (void)x;
>results in undefined behaviour?

Assuming you mean for x to be an uninitialized auto variable,
yes, that would be undefined, because the operand of (void) is
evaluated.

> what about:
>int foo(void)
>{
> return;
>}
>int main(void)
>{
> foo();
> (void)foo();
> return 0;
>}

Yes, that's undefined for similar reasons, and in C9x the definition
of foo() will be a constraint error (return type mismatch).

> Also consider:


> int x;
> x;
>Is x evaluated here?

Certainly x is evaluated there.
I don't see why there is any question about it.

> I see nothing to make is less defined than:

> &x;

Unary & does not evaluate its operand.

>or perhaps closer still:
> sizeof x;

sizeof does not evaluate its operand.

>>> for (i = 0; i < sizeof p; i++)
>>> ((unsigned char *)&q)[i] = ((unsigned char *)&p)[i];

>What if p is an uninitialised automatic variable?

No problem, for exactly the same reason as for invalid pointer.

> According to 6.5.7 the
>value of such an object is indeterminate.

It's a bit too glib; the value of the declared auto object is
indeterminate (per the declared type) as a general rule, but ...

> unsigned char x, y=x;
> unsigned char a, b;
> memcpy(&a, &b, 1);

.... these should work okay anyway, due to the extraordinary
guarantee that live objects can be accessed via aliasing to a
character type. There should be at least a footnote added to
clarify this.

>How far does this magic property of unsigned char extend?

It's not really magic; it's a logical consequence of objects
being composed of bytes, combined with all bits of a char=byte
being significant, combined with the requirement for integer
(including char=byte) representation to conform to a strict
binary numeration model.

> Is there anything
>preventing an implementation only making an object (or more specifically the
>bytes that make up an object) readable for any purpose after it is first
>written to/initialised?

Yes, in addition to the above guarantee that the bytes as such
have meaning, the object lifetime comes into play. (For an auto
variable, its lifetime starts at the declaration and ends at the
end of the immediately-enclosing block.)


Douglas A. Gwyn

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

In article <880582...@genesis.demon.co.uk>,

fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:
>{
> int array[2];
> int *ptr;
> ptr = array;
> &*++ptr; /* Not undefined */

This is not undefined, because *++ptr (as such) is not evaluated.
(However, ptr is evaluated in order to perform the ++ operation.)
The key to understanding all this is "lvalues are not values".

> ptr = array;
> *++ptr; /* ? */

This is undefined, because the expression in an expression
statement *is* evaluated.

> ptr = array;
> (void)*++ptr; /* Undefined ? */

This is undefined, because a cast operator converts the value of
its operand to the named type, so the operand is evaluated.


Christian Bau

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

> So to be clear does that mean that:
>
> int x;
>
> (void)x;
>

> results in undefined behaviour? what about:


>
>
> int foo(void)
> {
> return;
> }
>
> int main(void)
> {
> foo();
>
> (void)foo();
>
> return 0;
> }
>

> It is generally accepted that foo(); doesn't "use" the return value of
> foo(), but what about (void)foo(); ? If what you say above is correct then
> this does use the value and results in undefined behaviour, since a cast

> does evaluate its operand. Also consider:
>
> int x;
>
> x;
>

> Is x evaluated here? On reflection I don't think so since no operator is
> applied to it to take its value.

Is (void) x really a cast that converts the value of x into a value of
type void, or does it just explicitely ignore the value? In your first
example, it is agreed that

foo ();

doesnt "use" the return value but just ignores it, and therefore it is ok
if foo () didnt return a value for this call. The reason why I might write

(void) foo ();

instead is to tell the compiler and the reader of this code that I know
that foo () is declared as a function returning a value, and that my
ignoring that value is not a mistake, but done on purpose. I would hope
that something that looks like a cast to void just ignores the value, and
that therefore the statements

foo ();
and
(void) foo ();

are completely equivalent.

If that is the case, then it seems logical that the statements

(void) x;
and
x;

should also be equivalent.

Lawrence Kirby

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

In article <65j7s5$m...@dfw-ixnews9.ix.netcom.com>

gw...@ix.netcom.com "Douglas A. Gwyn" writes:

>In article <880582...@genesis.demon.co.uk>,
> fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:
>>{
>> int array[2];
>> int *ptr;
>> ptr = array;
>> &*++ptr; /* Not undefined */
>
>This is not undefined, because *++ptr (as such) is not evaluated.
>(However, ptr is evaluated in order to perform the ++ operation.)
>The key to understanding all this is "lvalues are not values".

Right. OK, 6.2.2.1 is the key here since it states when an lvalue is
converted to a value...


>
>> ptr = array;
>> *++ptr; /* ? */
>
>This is undefined, because the expression in an expression
>statement *is* evaluated.

...in fact 6.2.2.1 states when an lvalue isn't converted to a value and
this isn't one of those cases. I suppose you could also look to 6.6.3 but
"evaluated as a void expression" it a little vague.

>> ptr = array;
>> (void)*++ptr; /* Undefined ? */
>
>This is undefined, because a cast operator converts the value of
>its operand to the named type, so the operand is evaluated.

So it follows from this that:

int foo(void)
{
return;
}

int main(void)
{
(void)foo();
return 0;
}

results in undefined behaviour. However what about:

int main(void)
{
foo();
return 0;
}

What does "evaluated as a void expression" mean? Does it mean it is evaluated
as (void)foo() in which case it is undefined, or something else? Maybe this
should be expressed more clearly e.g. "the expression is evaluated but the
result is not used". Since "use" seems to be the key word in this sort of
situation is makes sense to include it in the definition.

Lawrence Kirby

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

In article <christian.bau-2...@christian-mac.isltd.insignia.com>
christ...@isltd.insignia.com "Christian Bau" writes:

...

>Is (void) x really a cast that converts the value of x into a value of
>type void, or does it just explicitely ignore the value?

6.3.4 which defines cast operators says "Preceding an expression by a
parenthesized type converts the value of the expression to the named type."

There are no exceptions (barring constraints) so *any* valid cast takes the
value of the expression that is its operand.

>In your first
>example, it is agreed that
>
> foo ();
>
>doesnt "use" the return value but just ignores it, and therefore it is ok
>if foo () didnt return a value for this call. The reason why I might write
>
> (void) foo ();
>
>instead is to tell the compiler and the reader of this code that I know
>that foo () is declared as a function returning a value, and that my
>ignoring that value is not a mistake, but done on purpose. I would hope
>that something that looks like a cast to void just ignores the value, and
>that therefore the statements
>
> foo ();
>and
> (void) foo ();
>
>are completely equivalent.

Apparently not. I think that 6.3.4. should cover (void) as a separate case
in the semantics section because there are a number of cases it doesn't
really cover adequately. It talks about "value" but it is questionable
whether that term can be applied to expressions with incomplete or function
type.

>If that is the case, then it seems logical that the statements
>
> (void) x;
>and
> x;

It would certainly be logical.

Joe Messen

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

Douglas A. Gwyn wrote:
>
> In article <880566...@genesis.demon.co.uk>,
> fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:
>
[snip]
>
> > what about:

> >int foo(void)
> >{
> > return;
> >}
> >int main(void)
> >{
> > foo();
> > (void)foo();
> > return 0;
> >}
>
> Yes, that's undefined for similar reasons, and in C9x the definition
> of foo() will be a constraint error (return type mismatch).
>
> > Also consider:
> > int x;
> > x;
> >Is x evaluated here?
>
> Certainly x is evaluated there.
> I don't see why there is any question about it.
>
if:
int main(void) {
int x;
x;
return 0;
}
is undefined because the expression "x;" evaluates the value of x,
can you explain why the following wouldn't be undefined?

int foo(void) {
return;
}

int main(void) {
foo();
return 0;
}

This would lead me to believe that there is a fundamental difference
between a function call and any other expression, which is:

Any other expression is evaluated even if you don't _use_ the value,
as in x; A function call is evaluated _only_ if the value is
used, therefore foo();'s value is not accessed and does not cause
anything to fly out of your nose :)

Is that true?

--
p.s. Please remove the_obvious when replying by e-mail.

Joe Messen

unread,
Nov 27, 1997, 3:00:00 AM11/27/97
to

Douglas A. Gwyn wrote:
>
> In article <347DEC72...@total.the_obvious.net>,
> Joe Messen <ny...@total.the_obvious.net> wrote:

> >can you explain why the following wouldn't be undefined?
> >int foo(void) {
> >return;
> >}
> >int main(void) {
> >foo();
> >return 0;
> >}
>

> That's simply a contradiction of the type system that had to be
> blessed to "grandfather in" antique code written when there was
> no "void" type in C, along with implicit "int" in declarations.
> Because it's a contradiction, there is no good way to specify it,
> although it would have been better to say explicitly that this
> was an exception to the general rule.
>
[snip]


>
> >This would lead me to believe that there is a fundamental difference
> >between a function call and any other expression, which is:
> >Any other expression is evaluated even if you don't _use_ the value,
> >as in x; A function call is evaluated _only_ if the value is
> >used, therefore foo();'s value is not accessed and does not cause
> >anything to fly out of your nose :)
>

> That doesn't work, because if one doesn't evaluate the function-call
> expression then one can't call the function. The returned value

I meant to say;
The _value_ of any other expression is evaluated even ........,
not so the _value_ of a function expression.

> *might* be used; it depends on the exact calling context. The
> orderly thing to do is to remove the special dispensation for
> non-void functions that do not return values. The alternative is
> to clearly specify that this kludge is a special case.
Indeed. It should be clearly specified.

Douglas A. Gwyn

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

In article <christian.bau-2...@christian-mac.isltd.insignia.com>,
christ...@isltd.insignia.com (Christian Bau) wrote:

>I would hope
>that something that looks like a cast to void just ignores the value, ...

The problem is, there has to be a value to be ignored, i.e. the operand
of any cast operator is evaluated. We could have made an exception for
(void) but I don't think we did.


Douglas A. Gwyn

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

In article <880653...@genesis.demon.co.uk>,

fr...@genesis.demon.co.uk (Lawrence Kirby) wrote:
>>> ptr = array;
>>> *++ptr; /* ? */
>>This is undefined, because the expression in an expression
>>statement *is* evaluated.
>....in fact 6.2.2.1 states when an lvalue isn't converted to a value and

>this isn't one of those cases.

Your explanation for this case is better than mine:
There is an lvalue (see the semantics for the * operator in 6.3.3.2)
and it is converted to a value, because this is not one of the
contexts enumerated in 6.2.2.1 where it isn't converted to a value.

> I suppose you could also look to 6.6.3 but
>"evaluated as a void expression" it a little vague.

Yeah, that's what I was trying to apply to this case, but in the
light of further discussion I don't think I should have used 6.6.3
for this; 6.2.2.1 is appropriate.

"void expression" is defined, more or less, in 6.2.2.2 and it is
clear that void expressions simply don't *have* values.

>int main(void)
>{
> foo();
> return 0;
>}

>What does "evaluated as a void expression" mean? Does it mean it is evaluated


>as (void)foo() in which case it is undefined, or something else?

6.6.3 says the "evaluation as a void expression" is done merely
to obtain side effects such as assignments and function calls.

I agree that there are some unintentionally unspecified aspects to
this; it can't mean equivalence to (void)-casting, but on the other
hand how can one evaluate the expression "foo();" when foo() fails
to return a valid value? You can make this more interesting:
foo(), bar();
i ? foo() : bar();
I think the intent is that the "nonexistence" of the value of the
void expression is supposed to save functions that don't return
proper values, so long as the only use of the returned value is
as the "outer" value in the void expression. I.e. if a void
expression *were* defined via (void)-casting (which it isn't),
then the evaluation would stop just short of needing an operand
value for the conversion to void.

I doubt this would matter were it not for grandfathering of non-void
functions that fail to return values. In the C9x draft we have made
some changes in this area, and it looks better, but I'm not sure all
the shadows have been illuminated.

> Maybe this
>should be expressed more clearly e.g. "the expression is evaluated but the
>result is not used". Since "use" seems to be the key word in this sort of
>situation is makes sense to include it in the definition.

The only way that would really help would be if there were a clear
statement of where values are used and where they are not. My own
criterion has been that they are used when there is an evaluation
and not otherwise.

This is the sort of thing that argues for use of formal semantics
in the language specification; however, C requires too many things
to be loosely specified (due to its systems programming heritage)
to make that a viable approach. C9x does tighten it up somewhat.


Douglas A. Gwyn

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to
>> > Also consider:
>> > int x;
>> > x;
>> >Is x evaluated here?
>> Certainly x is evaluated there.
>> I don't see why there is any question about it.

I should add that it is "evaluated as a void expression",
although the exact semantics of that are not crystal clear.
(See a nearby discussion with L. Kirby.)
I would say that the evaluation of the "x" part fetches
the value of x and that fetch constitutes undefined behavior.

>can you explain why the following wouldn't be undefined?
>int foo(void) {
>return;
>}

>int main(void) {
>foo();
>return 0;
>}

That's simply a contradiction of the type system that had to be


blessed to "grandfather in" antique code written when there was
no "void" type in C, along with implicit "int" in declarations.
Because it's a contradiction, there is no good way to specify it,
although it would have been better to say explicitly that this
was an exception to the general rule.

C9x is slated to get rid of implicit ints, also to make the above
definition of foo() not strictly conforming, although (in my
opinion) it doesn't quite go far enough in that it allows the
trailing } of a function definition to be "executed" in a non-void
function so long as the functions value "is not used" (that part
is similar to the C89 grandfathering kludge). Unfortunately I
missed the meeting where this was decided (London), because I
think the technical difficulty the committee saw in making
reaching the } fully equivalent to "return;" could have been
resolved so that we could have done the right thing and stop
grandfathering *all* forms of this abuse of the type system.
If we get a public comment to this effect during balloting,
I'll gladly champion the cause.

>This would lead me to believe that there is a fundamental difference
>between a function call and any other expression, which is:
>Any other expression is evaluated even if you don't _use_ the value,
>as in x; A function call is evaluated _only_ if the value is
>used, therefore foo();'s value is not accessed and does not cause
>anything to fly out of your nose :)

That doesn't work, because if one doesn't evaluate the function-call
expression then one can't call the function. The returned value

Lawrence Kirby

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

In article <65icg8$cms$4...@darla.visi.com>
se...@plethora.net "Peter Seebach" writes:

>In article <880582...@genesis.demon.co.uk>,


>Lawrence Kirby <fr...@genesis.demon.co.uk> wrote:
>>Yes, that is certainly true. However & also evaluates its operand for
>>side-effects but it doesn't take its value. OK, I'll reverse one thing
>>I wrote: & is a better example than sizeof.
>
>Hmm. Point...
>
>>{

>> int array[2];
>> int *ptr;
>
>> ptr = array;
>> &*++ptr; /* Not undefined */
>

>> ptr = array;
>> *++ptr; /* ? */
>

>I say undefined, because the value of the expression is the indeterminate
>thing.

The question was whether this expression uses the value. As I mentioned
in my reply to Doug 6.2.2.1 defines when an lvalue is "converted to a value"
and it is in this case. That is probably enough to show that the value is
taken. 6.6.3 (Expression and null statements) says this is "evaluated as
a void expression for its side effects". The "as a void expression" probably
supports that view but I think it is a rather vague wording.

> Note that in C9X, we actually have wording to explicitly cover the
>intent that '&*' and '*&' are both semantically null, and that you really
>aren't using the value of a thing you take the address of, and this happens
>because & is suppressiing part of the evaluation.

I hope it also covers casts to void saying in effect that they don't use
the result of their operand. The current siuation where they do results
in too many warts and undesirable (lack of defined) behaviour (that's a
new one!)

>However, my understanding is that, when asked, the committee has said that
>this is the intended meaning of the current standard.
>

>> ptr = array;
>> (void)*++ptr; /* Undefined ? */
>

>I say it is.

Good (as far as the current standard is concerned).

Douglas A. Gwyn

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

>> Note that in C9X, we actually have wording to explicitly cover the
>>intent that '&*' and '*&' are both semantically null, and that you really
>>aren't using the value of a thing you take the address of, and this happens
>>because & is suppressiing part of the evaluation.
>>However, my understanding is that, when asked, the committee has said that
>>this is the intended meaning of the current standard.

Actually we said the opposite, but the majority was not happy about it.
We said the opposite because a strict reading of the C89 rules forced
that interpretation. With C9x there is a chance to "fix" it if proper
words can be found; we tried before but didn't get the words right.
This part of the C9x draft should be looked at closely to see if it
allows &* and *& in the proper places without having any undesired
consequences.


Michael Norrish

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

Douglas A. Gwyn <gw...@ix.netcom.com> writes:

[...]



> This is the sort of thing that argues for use of formal semantics in
> the language specification; however, C requires too many things to
> be loosely specified (due to its systems programming heritage) to
> make that a viable approach. C9x does tighten it up somewhat.

Did this man just rattle my cage or what!? :-)

While it is true that the specification of the library's behaviour
would probably be difficult, the looseness of the specifications of
the core language's behaviour is *not* a problem for a formal
semantics.

It makes using the formal semantics to verify programs difficult, but
there is no conceptual difficulty.

For example, here is my theorem specifying the numeric properties of
the type size constants:

(where ^ is exponentiation, and (?x. ....x...) means there exists an
x such that whatever property holds)


|- LONG_MIN <= INT_MIN and INT_MIN <= SHRT_MIN and SHRT_MIN <= SCHAR_MIN
and
SCHAR_MAX <= SHRT_MAX and SHRT_MAX <= INT_MAX and INT_MAX <= LONG_MAX
and
SCHAR_MIN <= -127 and 127 <= SCHAR_MAX and
(SCHAR_MAX = (UCHAR_MAX - 1) div 2)
and
255 <= UCHAR_MAX and (?x. UCHAR_MAX = 2^x - 1))
and
SHRT_MIN <= -32767 and 32767 <= SHRT_MAX and
SHRT_MAX = (USHRT_MAX - 1) div 2)
and
65535 <= USHRT_MAX and (?x. USHRT_MAX = 2^x - 1))
and
INT_MIN <= -32767 and 32767 <= INT_MAX and
(INT_MAX = (UINT_MAX - 1) div 2)
and
65535 <= UINT_MAX and (?x. UINT_MAX = 2^x - 1))
and
LONG_MIN <= -2147483647 and 2147483647 <= LONG_MAX and
(LONG_MAX = (ULONG_MAX - 1) div 2)
and
4294967295 <= ULONG_MAX and (?x. ULONG_MAX = 2^x - 1)

As it happens, I don't believe this is quite correct, in that it
doesn't say enough. In particular, I think I probably need extra
clauses along the lines of
|LONG_MIN| <= LONG_MAX (where |x| = absolute value of x)
(The problem is that figuring out all the consequences of the
requirements stated in the standard is quite tricky, and its not
immediately obvious for example what requirements the standard makes
on the systems used for encoding numbers.)

In any case, this just illustrates the moral of the story, which is
that underspecification is not an obstacle to formalisation.

Michael.


Clive D.W. Feather

unread,
Nov 28, 1997, 3:00:00 AM11/28/97
to

In article <880328...@genesis.demon.co.uk>, Lawrence Kirby
<fr...@genesis.demon.co.uk> writes

>>> memcpy(&q, &p, sizeof(int*)) /* where q is an int* as well */
>>
>>This is defined in C9X to work on a byte-by-byte basis. The
>>indeterminate value is copied in a strictly conforming manner. At least,
>>that's how I interpret the words I wrote.
>
>What about:

>
> for (i = 0; i < sizeof p; i++)
> ((unsigned char *)&q)[i] = ((unsigned char *)&p)[i];
>
>Does it make any difference whether p is an uninitialised pointer or a
>pointer to freed memory?

I think the same applies - the value is being accessed as an array of
unsigned chars.

--
Clive D.W. Feather | Director of Software Development | Home email:
Tel: +44 181 371 1138 | Demon Internet Ltd. | <cl...@davros.org>
Fax: +44 181 371 1037 | <cl...@demon.net> |
Written on my laptop; please observe the Reply-To address |

Bernard A. Badger

unread,
Nov 30, 1997, 3:00:00 AM11/30/97
to

Douglas A. Gwyn wrote in message <65l29k$c...@sjx-ixn8.ix.netcom.com>...
Does this mean that you cannot cast to void twice?

As in:

int x;

(void) (void) x;

A programmer probably wouldn't knowingly do this, but it might occur as a
result
of macro expansion.


Douglas A. Gwyn

unread,
Dec 1, 1997, 3:00:00 AM12/1/97
to

In article <YW5g.4532$U01.1...@news.internetMCI.com>,

"Bernard A. Badger" <bernard...@MCI2000.com> wrote:
>Does this mean that you cannot cast to void twice?

That's not clear. 6.1.2.5 says "The void type comprises an
empty set of values", which make it sound both like there
are void values, but there aren't any void values.
Personally I would allow (void) to be applied to an operand
of void type.


Roger_...@compsys.com.au

unread,
Dec 5, 1997, 3:00:00 AM12/5/97
to

In article <880329...@genesis.demon.co.uk>,
fr...@genesis.demon.co.uk wrote:
> Well, I suppose there is always the "one past the end of the object"
> pointer and of course there is the null pointer. Both of these are valid
> pointer values but don't point to an object.

How about this then...

Imagine you have an array in memory immediately followed by a block of
allocated memory. (That could be possible - although not in any
implementation I know of, but still possible). Then that block of memory
is then freed.

Then the pointer to one past the end of the array is also now a pointer to
freed memory.

Is it indeterminate?

eg.

int array[10];
int*p;int*q;
p = array+10; /* one past the end of the array */
q = malloc(...); /* happens to allocate just after 'array' */
/* so now p == q in this particular implementation */
free(q);

Is p indeterminate?

By the 'one past end of array' rule it is.
By the 'pointer to freed memory' rule it isn't.

Who wins?

Roger

-------------------==== Posted via Deja News ====-----------------------
http://www.dejanews.com/ Search, Read, Post to Usenet

0 new messages