why aren't cast expressions lvalvues? 
Author Message
 why aren't cast expressions lvalvues?

I'm converting some code from assembler to C, and it has some data
structures that don't map nicely into C: the field I want is offset
by a variable length from the beginning of the object, with the length
stored at a fixed offset from the beginning.

the field has type (void*)      // no comments please, I'm just maintaining

p is a pointer to my object
p->size is a field that tells the size of the variable length part

I'd like to have something close to p->field, so I wrote a macro

#define FIELD(p) ((void *)*(unsigned long *)((unsigned char *)(p) +
(p)->size))

i'd like to be able to use FIELD(p) on either side of an assignment, but
my usual complier compains that (void *)... is not an lvalue.

I found that gcc 1.37 compiles it just fine, and produces correct code.

I also found that using *(void **)& in place of (void *) also works. Why
do these differ? Why do they both work with gcc?

also, is there a canonical "pointer type" I could use in place of
(unsigned long *) in the macro?

thanks,
mike



Mon, 18 Sep 1995 09:27:47 GMT  
 why aren't cast expressions lvalvues?

Quote:

> ...
>I also found that using *(void **)& in place of (void *) also works. Why
>do these differ? Why do they both work with gcc?

C does not guarantee that all pointer types are the same size.  The
expression *(void **)(address_expr) accesses sizeof(void*) bytes of
memory.  Always.  Your example just happened to work.  In fact, I
believe that for any address_expression,
    *(void **)(address_expression)
is equivalent to
    (void *)(address_expression)
if and only if sizeof(address_expresion)==sizeof(void*).  And for
a given compiler, this may be true for some types and not others.

Quote:
>also, is there a canonical "pointer type" I could use in place of
>(unsigned long *) in the macro?

Not portably, for the reasons stated above.

Quote:
>thanks,
>mike

--

3M Company                      phone:  (612) 737-3300
3M Center, Bldg. 235-3B-16      fax:    (612) 737-2700
St. Paul, MN 55144-1000


Tue, 19 Sep 1995 03:56:53 GMT  
 why aren't cast expressions lvalvues?


: I believe that for any address_expression,
:     *(void **)(address_expression)
: is equivalent to
:     (void *)(address_expression)
: if and only if sizeof(address_expresion)==sizeof(void*).

Not even close.  The first says "convert the value of the address
expression to type (void **), and fetch what the address expression
points at (treating it as a (void*) implicitly)".  The second says
"convert the value of the address expression to type (void*)".  Again,
the two are not even remotely similar.  They don't even talk about
the same bits, let alone the same interpretation of the same bits.

Kevin may have meant

         *(void**)(&pointer_object)    /* which is an lvalue */
    vs   (void*)(pointer_object)       /* which is not */

are equivalent in a value context.  At least both of these are talking
about the same bits, namely those in the pointer object.  But even
*these* aren't quite equivalent values on word oriented machines like
the DG MV series, some Prime computers, PDP10's, and so on.  In such
machines, values of type (char*) (and (void*)) typically have a
different bitwise internal format than pointers of other types, even if
they are the same size.  For example, on the DG MV series,

      struct foo f, fp=&f;
      *(void **)(&fp);

means to fetch the bits in the pointer fp (implicitly treating them as
if they were the bits of a pointer to void).  If such a pointer were
then itself indirected (after appropriate casting), it would most
usually get a SIGSEGV, because of misaligned ring protection fields in
the two pointer formats.

On the other hand

       struct foo f, fp=&f;
       (void *)fp;

mean to convert the value stored in the pointer fp to a pointer to void,
that is, fetch the same bits as before, but interpret them as a pointer
to struct, then *convert* the bits so that they are now in the format
of a pointer to void.  Future use of such a value would not SIGSEGV.

The bottom line is,   *((sometype*)p)++ = somevalue
and other grotesquely terse code for walking along in heterogeneously
typed memory, though common and even favored in bygone days because of
the common deficiency shared by pcc compilers of not diagnosing the
error in such espressions, was never guaranteed to work "properly", and
should be avoided like the plague.

Of course, even below the bottom line, gcc is pandering to incorrect
code by having a non-standard-conforming bug-compatible set of defaults
that supports such buggy old code.  I rather question the wisdom of
this.  For example:

        % cat >tmp.c
        main(){
            void *p;
            *((int*)p)++ = 0;
        }
        % gcc tmp.c
        % gcc -ansi -pedantic tmp.c
        tmp.c: In function main:
        tmp.c:3: invalid lvalue in increment

is one of many reasons I don't run gcc over my own code without
saying *at* *least* -ansi -pedantic.

IMNSHO, the best way of doing

               *((int*)p)++ = 0;            /* problem: not an lvalue */
is not
               *(*(int**)&p)++ = 0;
              /* now an lvalue, but: warning, warning, danger will robinson */
but would be
               *(int*)p = 0;
               p = (void*)((int*)p+1);       /* if p is (void*) */
or
               *(int*)p = 0;
               p += sizeof(int*);            /* if p is (char*) */
or even
               { int * intp = (int*)p;
                 *intp = 0;
                 p = (void*)(intp+1);        /* p is (void*) again */
               }

Yes, they are more verbose, but I've never felt any serious compulsion
to say the wrong thing as compactly as possible, any more than performance
issues make me yearn to get the wrong answer as quickly as possible.

Ignore that person behind the curtain posting to the "indian.c" thread.

--




Wed, 20 Sep 1995 01:45:36 GMT  
 why aren't cast expressions lvalvues?

Quote:

>Of course, even below the bottom line, gcc is pandering to incorrect
>code by having a non-standard-conforming bug-compatible set of defaults
>that supports such buggy old code.

I strongly disagree.  (Have you ever read the ``GNU Extensions to the
C Language'' section of the manual?)  First I will excerpt the
description of the feature that Wayne is referring to, and then I'll
explain why I think it's a feature.

------------------------------------
   A cast is a valid lvalue if its operand is an lvalue.  A simple
assignment whose left-hand side is a cast works by converting the
right-hand side first to the specified type, then to the type of the
inner left-hand side expression.  After this is stored, the value is
converted back to the specified type to become the value of the
assignment.  Thus, if `a' has type `char *', the following two
expressions are equivalent:

     (int)a = 5
     (int)(a = (char *)(int)5)

   An assignment-with-arithmetic operation such as `+=' applied to a
cast performs the arithmetic using the type resulting from the cast,
and then continues as in the previous case.  Therefore, these two
expressions are equivalent:

     (int)a += 5
     (int)(a = (char *)(int) ((int)a + 5))
------------------------------------

First off, note that this is NOT repeat NOT the same behavior as `pcc'
exhibits.  Secondly, note that the expressions which can be built in
this way are simply syntactic sugar for the longer expressions with
more casts as shown.  I feel that it is fairly obvious what the
meaning of this construction should be (although because of PCC,
long-time users will likely disagree), and it saves some hard-to-parse
circumlocution, so there is no real harm and possible benefit in
supporting it as an extension.  (Yes, it makes it easier to write
nonsense like Wayne listed in his article, but it also makes it easier
to recognize when nonsense is being written.)

I will agree that GCC should disgnose this in `-ansi' mode without
`-pedantic'.  Here is a patch for version 2.3.1, which should work for
all 2.3 versions.

*** /usr/src/gnu/gcc/gcc-2.3.1/c-typeck.c.orig  Fri Apr  2 17:40:45 1993
--- /usr/src/gnu/gcc/gcc-2.3.1/c-typeck.c       Fri Apr  2 17:45:56 1993
***************
*** 53,57 ****
  static char *get_spelling ();
  tree digest_init ();
! static void pedantic_lvalue_warning ();
  tree truthvalue_conversion ();
  void incomplete_type_error ();
--- 53,57 ----
  static char *get_spelling ();
  tree digest_init ();
! static void generalized_lvalue_warning ();
  tree truthvalue_conversion ();
  void incomplete_type_error ();
***************
*** 3023,3027 ****
                {
                  tree incremented, modify, value;
!                 pedantic_lvalue_warning (CONVERT_EXPR);
                  arg = stabilize_reference (arg);
                  if (code == PREINCREMENT_EXPR || code == PREDECREMENT_EXPR)
--- 3023,3027 ----
                {
                  tree incremented, modify, value;
!                 generalized_lvalue_warning (CONVERT_EXPR);
                  arg = stabilize_reference (arg);
                  if (code == PREINCREMENT_EXPR || code == PREDECREMENT_EXPR)
***************
*** 3287,3291 ****
      {
        tree real_result = build_unary_op (code, TREE_OPERAND (arg, 1), 0);
!       pedantic_lvalue_warning (COMPOUND_EXPR);
        return build (COMPOUND_EXPR, TREE_TYPE (real_result),
                    TREE_OPERAND (arg, 0), real_result);
--- 3287,3291 ----
      {
        tree real_result = build_unary_op (code, TREE_OPERAND (arg, 1), 0);
!       generalized_lvalue_warning (COMPOUND_EXPR);
        return build (COMPOUND_EXPR, TREE_TYPE (real_result),
                    TREE_OPERAND (arg, 0), real_result);
***************
*** 3295,3299 ****
    if (TREE_CODE (arg) == COND_EXPR)
      {
!       pedantic_lvalue_warning (COND_EXPR);
        return (build_conditional_expr
              (TREE_OPERAND (arg, 0),
--- 3295,3299 ----
    if (TREE_CODE (arg) == COND_EXPR)
      {
!       generalized_lvalue_warning (COND_EXPR);
        return (build_conditional_expr
              (TREE_OPERAND (arg, 0),
***************
*** 3305,3317 ****
  }

! /* If pedantic, warn about improper lvalue.   CODE is either COND_EXPR
     COMPOUND_EXPR, or CONVERT_EXPR (for casts).  */

  static void
! pedantic_lvalue_warning (code)
       enum tree_code code;
  {
!   if (pedantic)
!     pedwarn ("ANSI C forbids use of %s expressions as lvalues",
             code == COND_EXPR ? "conditional"
             : code == COMPOUND_EXPR ? "compound" : "cast");
--- 3305,3321 ----
  }

! /* If ANSI, warn about improper lvalue.   CODE is either COND_EXPR
     COMPOUND_EXPR, or CONVERT_EXPR (for casts).  */

  static void
! generalized_lvalue_warning (code)
       enum tree_code code;
  {
!   /*
!    * There should be a better flag meaning just ``-ansi was specified''
!    * rather than having to rely on this.
!    */
!   if (flag_no_nonansi_builtin)
!     warning ("ANSI C forbids use of %s expressions as lvalues",
             code == COND_EXPR ? "conditional"
             : code == COMPOUND_EXPR ? "compound" : "cast");
***************
*** 3816,3820 ****
        /* Handle (a, b) used as an "lvalue".  */
      case COMPOUND_EXPR:
!       pedantic_lvalue_warning (COMPOUND_EXPR);
        return build (COMPOUND_EXPR, lhstype,
                    TREE_OPERAND (lhs, 0),
--- 3820,3824 ----
        /* Handle (a, b) used as an "lvalue".  */
      case COMPOUND_EXPR:
!       generalized_lvalue_warning (COMPOUND_EXPR);
        return build (COMPOUND_EXPR, lhstype,
                    TREE_OPERAND (lhs, 0),
***************
*** 3824,3828 ****
        /* Handle (a ? b : c) used as an "lvalue".  */
      case COND_EXPR:
!       pedantic_lvalue_warning (COND_EXPR);
        rhs = save_expr (rhs);
        {
--- 3828,3832 ----
        /* Handle (a ? b : c) used as an "lvalue".  */
      case COND_EXPR:
!       generalized_lvalue_warning (COND_EXPR);
        rhs = save_expr (rhs);
        {
***************
*** 3878,3882 ****
                                    convert (TREE_TYPE (inner_lhs),
                                             convert (lhstype, newrhs)));
!       pedantic_lvalue_warning (CONVERT_EXPR);
        return convert (TREE_TYPE (lhs), result);
        }
--- 3882,3886 ----
                                    convert (TREE_TYPE (inner_lhs),
                                             convert (lhstype, newrhs)));
!       generalized_lvalue_warning (CONVERT_EXPR);
        return convert (TREE_TYPE (lhs), result);
        }

-GAWollman

--
Garrett A. Wollman   | Shashish is simple, it's discreet, it's brief. ...

uvm-gen!wollman      | It is a bond more powerful than absence.  We like people
UVM disagrees.       | who like Shashish.  - Claude McKenzie + Florent Vollant



Wed, 20 Sep 1995 06:51:57 GMT  
 why aren't cast expressions lvalvues?

Quote:
>Subject: why aren't cast expressions lvalvues?

I can't help you with that, but I could use it too.

Quote:
>also, is there a canonical "pointer type" I could use in place of
>(unsigned long *) in the macro?

I _think_ size_t is what you want.

--



Thu, 21 Sep 1995 01:01:54 GMT  
 why aren't cast expressions lvalvues?

Quote:
>>also, is there a canonical "pointer type" I could use in place of
>>(unsigned long *) in the macro?

>I _think_ size_t is what you want.

Size_t is the size of the integer value returned by sizeof.  It is typedefed in
stdlib.h (and maybe elsewhere) to be unsigned int, unsigned long, or unsigned short,
depending on the maximum size of an object.

It is not a pointer type.  

---

Eager Consulting                (415) 325-8077
1960 Park Boulevard, Palo Alto, CA 94306-1141



Thu, 21 Sep 1995 09:49:01 GMT  
 why aren't cast expressions lvalvues?

Quote:
>   C does not guarantee that all pointer types are the same size.

I remember reading about a way to interpret the standard which gives
all pointers the same representation.  It works like this: The
standard claims that a pointer to a structure is equal to a pointer to
the first member of the structure.  It also states that it's legal to
create a pointer to a structure whose members are unknown, an
incomplete struct.  This implies that a pointer to the first member of
a structure must have the same representation regardless of what data
type that member is.
--

OS/2 2.0 will do for me until AmigaDOS for the 386 comes along...
--

OS/2 2.0 will do for me until AmigaDOS for the 386 comes along...


Fri, 22 Sep 1995 17:51:44 GMT  
 why aren't cast expressions lvalvues?

Quote:
>I remember reading about a way to interpret the standard which gives
>all pointers the same representation.  It works like this: The
>standard claims that a pointer to a structure is equal to a pointer to
>the first member of the structure.  It also states that it's legal to
>create a pointer to a structure whose members are unknown, an
>incomplete struct.  This implies that a pointer to the first member of
>a structure must have the same representation regardless of what data
>type that member is.

No, it does not. Firstly, a struct cannot contain a function, only a pointer
to function. Therefore, you have only "proved" that a pointer to struct and
a pointer to pointer to function have the same size. Secondly, if all structs
are aligned e.g. at word boundaries, pointers to struct might be smaller,
without loss of information (i.e. you can still cast between the struct pointer
and the pointer to the first member).

-----------------------------------------------------------------------------
Harald Winroth     | Computational Vision and Active Perception Laboratory,

-----------------------------------------------------------------------------



Sat, 23 Sep 1995 07:21:37 GMT  
 why aren't cast expressions lvalvues?

Quote:
>I remember reading about a way to interpret the standard which gives
>all pointers the same representation.

Nope.  All struct pointers must have the same representation, regardless of
the kind of struct.  All union pointers must have the same representation.
The proof is a just a bit difficult.
(All int pointers must have the same representation.  Etc.  Much easier.)

Quote:
>It works like this: The standard claims that a pointer to a structure is
>equal to a pointer to the first member of the structure.

Will point there, if suitably cast.  It doesn't have to have the same
representation itself, while pointing there.

Quote:
>It also states that it's legal to create a pointer to a structure whose
>members are unknown, an incomplete struct.

That is true.

Quote:
>This implies that a pointer to the first member of a structure must have
>the same representation regardless of what data type that member is.

No, it does not imply that.
--

If this were the company's opinion, I wouldn't be allowed to post it.
Beware the third {*filter*}ly transmitted disease of the computer world:  __STDC__


Sat, 23 Sep 1995 09:24:56 GMT  
 why aren't cast expressions lvalvues?



: >Of course, even below the bottom line, gcc is pandering to incorrect
: >code by having a non-standard-conforming bug-compatible set of defaults
: >that supports such buggy old code.
: I strongly disagree.   I will excerpt the
: description of the feature that Wayne is referring to, and then I'll
: explain why I think it's a feature.

Well, since an infelicitous phrase of mine has been caught or killed,
let me hereby disavow any malign intent in its utterance.

In particular, I agree with Garrett that gcc's definition of the
extension in question is a reasonable (perhaps even "good") one, in
that the behavior is well defined and thought out, and not an
unintentional side effect of an unchecked intermediate representation
hack as in pcc, and that in general this extension is probably a pretty
neat idea.  At least as good as digital watches.

What I question is its inclusion into gcc's standard behavior, because
this inclusion tends to perpetuate buggy code developed under pcc
without warning.  Even -Wall doesn't provoke a warning about this.
Unfortunately, it's hard to propose a concrete way to improve gcc's
strategy here, because things quickly degenerate into a large set of
twisty warning messages, all different, and all with different degrees
of payoff in particular circumstances.  Perhaps a -Wpcc-bugs would be
nice... or maybe some elaborate keyword-coded control (file or command
line) syntax for controlling warning and error messages, like Gimpel
lint, or Abraxis codecheck.  (Ugh.)  

Anyway, with the current set of controls on warning messages (as
Garrett mentions) the only way to provoke warning about this is with
-pedantic, and the implied comment that warning about this is of
interest only to pedants is questionable at best.

But back to the central point... it still seems somehow unsanitary to
me to allow old buggy pcc-inspired code to pass muster, despite the
relative cleanliness of gcc's definition of cast-as-lvalue (or lvalvue),
and despite the fact that the usual exploitation of this pcc bug
results in the Right Thing being done by gcc.  It just strikes me
as too close to a case of mutant misbugs becoming feeping creatures...
--




Sat, 23 Sep 1995 00:27:22 GMT  
 why aren't cast expressions lvalvues?

Quote:

>>   C does not guarantee that all pointer types are the same size.
> I remember reading about a way to interpret the standard which gives
> all pointers the same representation.

The ability to use pointers to incomplete struct types means that all
struct pointers have to be similar in some ways - all the same size,
for example.

Quote:
> It works like this: The standard claims that a pointer to a structure
> is equal to a pointer to the first member of the structure.

No; they aren't even of the same type, so (except for the special case
where the member is of type void *, which causes a silent conversion)
it's not allowed to compare them.  What is promised is that if you
convert a pointer to a structure to the type of a pointer to its first
member, the result points to that member.  Notice that you cannot
perform this conversion when the type of that first member is unknown.

Quote:
> It also states that it's legal to create a pointer to a structure
> whose members are unknown, an incomplete struct.  This implies that a
> pointer to the first member of a structure must have the same
> representation regardless of what data type that member is.

Sorry.  All it implies is that if you somehow manage to determine what
the type of the first member of the structure actually is, and cast to
that type, the pointer will point to that first member.

Since this is going to comp.std.c as well as comp.lang.c, I should
probably include a language-lawyer question.  Consider a module
containing nothing but the following code:

struct a;
void foo(struct a *aptr)
{
 *(char *)aptr = 'x';

Quote:
}

Is this code guaranteed to do what it appears to be trying to do when
called with a pointer to any struct whose first member is a char?  Even
if that struct isn't tagged "a"?  Even if this is done with two
different struct types in the same program (presumably with different
prototypes for foo in scope)?  Consider, for example, the following
module, both with and without WITH_C defined at compile time:

struct b { char c; int i; };
struct c { char c; char d; char e; };

extern void blee(struct b *, struct c *);

void bar(void)
{
 struct b b;
 struct c c;
  { void foo(struct b *);
    foo(&b); /* is this guaranteed to put 'x' in b.c? */
  }
#ifdef WITH_C
  { void foo(struct c *);
    foo(&c); /* is this guaranteed to put 'x' in c.c? */
  }
#endif
 blee(&b,&c);

Quote:
}

                                        der Mouse




Tue, 26 Sep 1995 10:46:46 GMT  
 why aren't cast expressions lvalvues?

Quote:

>> It works like this: The standard claims that a pointer to a structure
>> is equal to a pointer to the first member of the structure.
>No; they aren't even of the same type, so (except for the special case
>where the member is of type void *, which causes a silent conversion)
>it's not allowed to compare them.

They aren't even of the same type, even in the special case where the member
is of type void *.  A pointer to the first member would have type void **.
(And even if there were a silent conversion, that would prove that the types
were not the same before the conversion -- but anyway there isn't.)

(Hey Ms. Degener, I got it right this time!)

--

Quote:
>Consider a module containing nothing but the following code:
>  struct a;
>  void foo(struct a *aptr) { *(char *)aptr = 'x'; }
>Is this code guaranteed to do what it appears to be trying to do when
>called with a pointer to any struct whose first member is a char?

The pedantic question has been raised in the past, but not yet answered,
does (void*)&something still point to something?  Presumably an interpretation
ruling will say yes, if we ever get one.  If so, then it's pretty clear that
this will have to work too, assuming that the argument actually points at an
object (which cannot have zero size in standard C).

Quote:
>Even if that struct isn't tagged "a"?

You mean if the actual object being pointed to is a struct something_else?
Then I think the only way to fail is for a very pedantic implementation
to keep type information around at run-time and check for this.  In other
words, pedantically no, but practically yes, I think.

Quote:
>Even if this is done with two different struct types in the same program
>(presumably with different prototypes for foo in scope)?

Do you mean that two other translation units declare different prototypes,
and there are now a total of three translation units in your program?
Or there is one other translation unit with two different block-scoped
declarations, and there are now a total of two translation units?
In the latter case, a compiler could very easily detect that you have
violated a "shall", and it doesn't have to issue a diagnostic, but it can
if it wishes, and it can reject your program if it wishes.
In the former case, a type-checking link step would be enough to reject
your program (and is also allowed to do so).  Well, type-checking linkers
have only been known for around 30 years or so, so maybe C lovers don't
have to worry about them.
--

If this were the company's opinion, I wouldn't be allowed to post it.
Beware the third {*filter*}ly transmitted disease of the computer world:  __STDC__


Tue, 26 Sep 1995 11:56:00 GMT  
 
 [ 15 post ] 

 Relevant Pages 

1. why aren't my random numbers random ?

2. why aren't these the same?

3. Why aren't my static pointers zeroed?

4. why CRichEditCtrl fonts aren't changing

5. full type declarations within cast expressions or unary expressions

6. Can't believe these aren't fixed in VS.NET

7. Why cast malloc's return?

8. Why can't overload type cast to base type (object)

9. Guide settings for dialog editor aren't saved

10. Errors aren't that simple

11. Language Stereotypes (Re: Errors aren't that simple)

12. Concatenating tokens that aren't parameters to macros in ANSI C

 

 
Powered by phpBB® Forum Software