[comp.lang.c] A cast pointer is NOT an lvalue!?

dal@midgard.mn.org (Dale Schumacher) (10/07/88)

In porting my standard library routines to a newly developed C compiler,
I ran into a problem in the printf() formatting code.  In order to take
an arbitrary object off of the stack, and move the argument pointer to
the "next" argument, I used a pointer cast construct like this:

   value = *((long *) argp)++;

The original 'argp' is typed as a int pointer.  Since I'm working closely
with the compiler authors, I was able to talk to them about the compiler
not handling this construct and was told that the X3J11 proposal states
clearly (in a footnote) that the result of a pointer cast is NOT an lvalue!
It appears to late to get this changed in the standard, but I would like
to know WHY.  As a clearer example of what I consider valid use of a cast
pointer, consider the following:

    struct big_thing { /* several elements */ } big, *bp;
    char *p;

    if(p = malloc(sizeof(struct big_thing)))
        {
        bp = ((struct big_thing *) p);
        *bp = big;
        }
    if(p = malloc(sizeof(struct big_thing)))
        *((struct big_thing *) p) = big;

The first example, using the intermediate 'bp', is (as I understand it)
valid ANSI C, but the second example is not.  Why do I need to assign the
cast value to an intermediate in order to dereference it and assign something
to it's contents?  It seems to me that the value of any cast to a pointer
type should result in a modifyable lvalue, even if the expression being
cast is not an lvalue, since you could cast thexpression to a pointer
type, assign it to a pointer variable and then dereference it.

chris@mimsy.UUCP (Chris Torek) (10/08/88)

In article <479@midgard.mn.org> dal@midgard.mn.org (Dale Schumacher) writes:
>   value = *((long *) argp)++;
>
>The original 'argp' is typed as a int pointer.  Since I'm working closely
>with the compiler authors, I was able to talk to them about the compiler
>not handling this construct and was told that the X3J11 proposal states
>clearly (in a footnote) that the result of a pointer cast is NOT an lvalue!
>It appears to late to get this changed in the standard, but I would like
>to know WHY.

Because a cast changes the format of the pointer.  What do you expect
`int i; ((float)i)++;' to do?  It is true that on a Vax or a Sun,
casting one pointer type to another does not in fact alter the bits.
But on a Data General MV/10000 series machine, or on some Lisp machines,
it *does*, and the new type and value appears only in a register.
The register is discarded immediately after the expression, so
incrementing it is useless.

In other words, while a pointer cast is *implemented* as a pun on
many machines, it is in principle a true conversion, and the result
is as ephemeral as the result of any other conversion.

>As a clearer example of what I consider valid use of a cast
>pointer, consider the following:

But this *is* valid:

>    struct big_thing { /* several elements */ } big, *bp;
>    char *p;
[...]
>    if(p = malloc(sizeof(struct big_thing)))
>        *((struct big_thing *) p) = big;
>
>...  Why do I need to assign the cast value to an intermediate in
>order to dereference it and assign something to it's contents?

You do not.  The result of a cast is NEVER an rvalue, but the result of
an indirection *is* an lvalue.  The following is legal, although the
results on a Data General are that the pointer is trashed, and the
results on most machines is that the integer is trashed:

	int i; char *p;
	...
	(*(int **)&p)++;
	*(float *)&i += .1;

>It seems to me that the value of any cast to a pointer
>type should result in a modifyable lvalue, even if the expression being
>cast is not an lvalue, since you could cast thexpression to a pointer
>type, assign it to a pointer variable and then dereference it.

The result of an indirection is most assuredly not the same as
expression being indirected!  (Among other things, it has a different
type.)
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

gwyn@smoke.ARPA (Doug Gwyn ) (10/09/88)

In article <479@midgard.mn.org> dal@syntel.UUCP (Dale Schumacher) writes:
>   value = *((long *) argp)++;

Rather than answer your questions directly, let me explain what is wrong
with this attempted usage.

A cast is the same as assignment to an unnamed temporary variable.
The ++ in your example would have to increment the temporary variable,
not the original variable argp.  That is clearly not what you wanted.

The result of a cast has never been an lvalue for just this reason.
Some compilers have been sloppy about this.

Anyway, you should be using either the old <varargs.h> or the new
<stdarg.h> macros to pick up arguments from a variable argument list.
What you were trying to do cannot be done portably any other way.

jejones@mcrware.UUCP (James Jones) (10/10/88)

In article <479@midgard.mn.org>, dal@midgard.mn.org (Dale Schumacher) writes:
  [ANSIoid compiler doesn't allow a cast to be used as an lvalue...]
> Since I'm working closely
> with the compiler authors, I was able to talk to them about the compiler
> not handling this construct and was told that the X3J11 proposal states
> clearly (in a footnote) that the result of a pointer cast is NOT an lvalue!
> It appears to late to get this changed in the standard, but I would like
> to know WHY.

This is, in fact, one of the places in which K&R is actually clear.  One can
readily show that a cast cannot be derived from the production for the non-
terminal "lvalue" in the back of the book.  ANSI Draft doesn't have this
production, so I would presume they felt an explicit statement was needed.

I'm not sure, though, that the example you give is really invalid!  A value
with pointer type does not have to be an lvalue to be dereferenced, so that

	*((woof *) p) = woof1;

is OK.  The sorts of constructs forbidden by a cast not being an lvalue are
these:

	{
		register int	i, *ip, *jp;

		((double) i) = 5.0;	/* what on earth is that supposed to do?! */
		((char *) ip) += i; /* someone wants to avoid scaling i, I guess */
		*((char *) ip)++ = *((char *) jp)++;
	}

Now, despite the fact that K&R and ANSI Draft both say or imply that a cast
is not an lvalue, it turns out that some C compilers will let some casts
get by.  (I just tried the second statement in the above block on a Sun 3,
and it compiled without a peep.  In fact, I would tend to wonder whether
most pcc-based compilers have the same, uh, feature, and how much code will
break when moved to an ANSI-conforming compiler.)

		James Jones

kjones@talos.UUCP (Kyle Jones) (10/11/88)

In article <479@midgard.mn.org> dal@syntel.UUCP (Dale Schumacher) writes:
>    if(p = malloc(sizeof(struct big_thing)))
>        *((struct big_thing *) p) = big;

This is valid.  The unary * converts cast pointer to an lvalue.

dal@midgard.mn.org (Dale Schumacher) (10/12/88)

In article <8663@smoke.ARPA> gwyn@brl.arpa (Doug Gwyn (VLD/VMB) <gwyn>) writes:
|In article <479@midgard.mn.org> dal@syntel.UUCP (Dale Schumacher) writes:
|>   value = *((long *) argp)++;
|
|A cast is the same as assignment to an unnamed temporary variable.
|The ++ in your example would have to increment the temporary variable,
|not the original variable argp.  That is clearly not what you wanted.

Ok.  This makes sense.  Since my posting I also discussed this with
Shane McCarron, who explained problems involving pointers of different
sizes and the possibility of distinct storage for different types,
which brings up a later question with Chris Torek's response.

|Anyway, you should be using either the old <varargs.h> or the new
|<stdarg.h> macros to pick up arguments from a variable argument list.
|What you were trying to do cannot be done portably any other way.

The printf()/scanf() code was written before I implemented <stdarg.h>
macros and I haven't gone back to change it yet.  I probably should.

In article <13912@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
|In article <479@midgard.mn.org> dal@midgard.mn.org (Dale Schumacher) writes:
|
|>As a clearer example of what I consider valid use of a cast
|>pointer, consider the following:
|
|But this *is* valid:
|
|>    struct big_thing { /* several elements */ } big, *bp;
|>    char *p;
|[...]
|>    if(p = malloc(sizeof(struct big_thing)))
|>        *((struct big_thing *) p) = big;
|>
|>...  Why do I need to assign the cast value to an intermediate in
|>order to dereference it and assign something to it's contents?
|
|You do not.  The result of a cast is NEVER an rvalue, but the result of
|an indirection *is* an lvalue.

Is this really legal in X3J11?  If an implementation stores different
object types in different areas of memory, then wouldn't that fail also?
How would one implement malloc() on such a machine?

diamond@csl.sony.JUNET (Norman Diamond) (10/12/88)

In article <479@midgard.mn.org>, dal@midgard.mn.org (Dale Schumacher) writes:
> I used a pointer cast construct like this:
> 
>    value = *((long *) argp)++;

Since you wanted a post-increment, it is trivial to do it properly:

     value = *((long *) argp++);

If you wanted to increment argp by the length of a (long *), then you
have to do:

     value = *(long *) argp;
     argp += (sizeof (long *)) / (sizeof argp);

which cannot give a correct result if sizeof (long *) is not a
multiple of sizeof (int *).  However, despite all the discussions
I've seen about various brain-damaged machines, none had *these*
two sizes unequal.  Anyway, it will work whenever your attempted
original version would work.
-- 
-------------------------------------------------------------------------------
  The above opinions are my own.   |   Norman Diamond
  If they're also your opinions,   |   Sony Computer Science Laboratory, Inc.
  you're infringing my copyright.  |   diamond%csl.sony.jp@relay.cs.net

chris@mimsy.UUCP (Chris Torek) (10/15/88)

In article <482@midgard.mn.org> dal@midgard.mn.org (Dale Schumacher) asks:
>... If an implementation stores different object types in different
>areas of memory, then wouldn't that fail also?  How would one implement
>malloc() on such a machine?

The short answer is `one would not'.  The long one is that maybe it
would work if the compiler recognised allocation calls (malloc, calloc)
and inserted special typing information.  At any rate, this sort of
thing is why `new' is a keyword in other languages.
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris

karl@haddock.ima.isc.com (Karl Heuer) (10/18/88)

In article <14005@mimsy.UUCP> chris@mimsy.UUCP (Chris Torek) writes:
>In article <482@midgard.mn.org> dal@midgard.mn.org (Dale Schumacher) asks:
>>... If an implementation stores different object types in different
>>areas of memory, then wouldn't that fail also?  How would one implement
>>malloc() on such a machine?
>
>The short answer is `one would not'.  The long one is that maybe it
>would work if the compiler recognised allocation calls (malloc, calloc)

On such an implementation, malloc() is not your only problem.  What about a
pointer which is (legally) cast to a less strictly aligned type and back
again?  What about a struct containing two different object types?

I think it's safe to say that no C implementation would depend on such type
segregation.

Karl W. Z. Heuer (ima!haddock!karl or karl@haddock.isc.com), The Walking Lint

wald-david@CS.YALE.EDU (david wald) (11/05/88)

In article <10034@socslgw.csl.sony.JUNET> diamond@csl.sony.JUNET (Norman Diamond) writes:
>In article <479@midgard.mn.org>, dal@midgard.mn.org (Dale Schumacher) writes:
>> I used a pointer cast construct like this:
>>
>>    value = *((long *) argp)++;
>
>If you wanted to increment argp by the length of a (long *), then you
>have to do:
>
>     value = *(long *) argp;
>     argp += (sizeof (long *)) / (sizeof argp);
>
>which cannot give a correct result if sizeof (long *) is not a
>multiple of sizeof (int *).

How about:

    value = *(long *)argp;
    argp = (argtype *)((char *)argp + sizeof(long *));

where argtype is the type of *argp?


============================================================================
David Wald                                              wald-david@yale.UUCP
						       waldave@yalevm.bitnet
============================================================================

chris@mimsy.UUCP (Chris Torek) (11/10/88)

In article <42234@yale-celray.yale.UUCP> wald-david@CS.YALE.EDU
(david wald) writes:
>How about:
>
>    value = *(long *)argp;
>    argp = (argtype *)((char *)argp + sizeof(long *));
>
>where argtype is the type of *argp?

You can write that, and it will probably compile, but it may not do
anything useful, and may even crash.  As a concession to weird
architectures, the dpANS grants license to store pointers to things of
varying sizes in cells of varying sizes.  In particular, if `argtype'
is, say, double, there may not be enough bits in it to retain a pointer
to a long, and code like

	while (argp < endp) {
		(void) printf("%ld\n", *(long *)argp);
		argp = (double *)((char *)argp + sizeof(long *));
	}

may loop forever printing the same value each time.

As a concession to getting work done, however, the dpANS grants license
to use something similar to varargs to do the job.  So why not use it
instead of trying to come up with an alternative?
-- 
In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163)
Domain:	chris@mimsy.umd.edu	Path:	uunet!mimsy!chris