By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
428,816 Members | 2,151 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 428,816 IT Pros & Developers. It's quick & easy.

cast-as-lvalue

P: n/a
I recently upgraded my Arch Linux system to GCC 3.4, and found out that a
previously accepted behavior (cast-as-lvalue) is now marked as deprecated,
and will cease to function in GCC 3.5. This has caused several builds to
break, most notably elfutils.

Presumably this behavior is not part of the C standard and it is thus
being excised like many other nonstandard GCC extensions. Regardless, my
question is not about the specifics or politics of GCC or any compiler.

Much of the code I'm trying to fix involves things like the following:

*((some_type *) ptr)++;

The desired behavior in this case is to increment the pointer as if it
were a (some_type *). In some cases, the pointer is typed as void, in
other cases it has a specific type, but regardless the compiler needs to
treat it as a different type when it is incrementing it.

Now I personally think cast-as-lvalue is a very logical way to attack this
problem, but apparently enough don't that it's been deprecated. My
question, then, is how to get the same exact behavior, but within the C
spec?

My current instinct is to do something as follows.

Assume the current code is:

foo *ptr;
*((bar *) ptr)++;

I would do the following:

foo *ptr;
bar *temp = (bar *)ptr;
*temp++;
ptr = (foo *)temp;

Is there any better way to do this? This seems like quite a bit of extra
code to avoid what (at least to me) looks like the logical way of
expressing this behavior.

-- Mike
Nov 14 '05 #1
Share this Question
Share on Google+
15 Replies


P: n/a

On Mon, 23 Aug 2004, Michael Baehr wrote:

The desired behavior in this case is to increment the pointer as if it
were a (some_type *). In some cases, the pointer is typed as void, in
other cases it has a specific type, but regardless the compiler needs to
treat it as a different type when it is incrementing it.
[not valid C code] foo *ptr;
*((bar *) ptr)++;
[valid C code] foo *ptr;
bar *temp = (bar *)ptr;
*temp++;
ptr = (foo *)temp;


This works, but you might as well compress it into one line and
lose the temporary:

foo *ptr;
ptr = (foo *)((bar *)ptr + 1);

Note that if 'foo' is 'void', you can drop the redundant cast, too:

void *ptr;
ptr = (bar *)ptr + 1;

HTH,
-Arthur
Nov 14 '05 #2

P: n/a
On Mon, 23 Aug 2004, Michael Baehr wrote:
I recently upgraded my Arch Linux system to GCC 3.4, and found out that a
previously accepted behavior (cast-as-lvalue) is now marked as deprecated,
and will cease to function in GCC 3.5. This has caused several builds to
break, most notably elfutils.

Presumably this behavior is not part of the C standard and it is thus
being excised like many other nonstandard GCC extensions. Regardless, my
question is not about the specifics or politics of GCC or any compiler.

Much of the code I'm trying to fix involves things like the following:

*((some_type *) ptr)++;


This isn't cast-as-lvalue but a normal cast. Standard C, though the
result is often undefined.

AFAIK, the equivalent cast-as-lvalue would be:

((some_type)*ptr)++;
Nov 14 '05 #3

P: n/a
On Tue, 24 Aug 2004, Jarno A Wuolijoki wrote:
On Mon, 23 Aug 2004, Michael Baehr wrote:
*((some_type *) ptr)++;


This isn't cast-as-lvalue but a normal cast. Standard C, though the
result is often undefined.


I re-engage my brain and take my word back:
*((some_type *) ptr)++; != (*((some_type *) ptr))++;

Sigh. Why is it that I always hit send before catching these details..
Nov 14 '05 #4

P: n/a
In article <pa****************************@spamblocked.com> ,
Michael Baehr <us*****@spamblocked.com> wrote:
*((some_type *) ptr)++;


As you note, this is not standard C (it was present in early C89
drafts, but removed). On machines where pointers to all types look
the same, declaring a union of pointer types is likely to work:

union {some_type *st; other_type *ot;} foo;
*foo.st++;

-- Richard
Nov 14 '05 #5

P: n/a
On Tue, 24 Aug 2004 00:51:57 +0300, Jarno A Wuolijoki wrote:
I re-engage my brain and take my word back:
*((some_type *) ptr)++; != (*((some_type *) ptr))++;

Sigh. Why is it that I always hit send before catching these details..


I think it's one of those unwritten laws of USENET.

Cheers ;)

-- Mike
Nov 14 '05 #6

P: n/a
# Much of the code I'm trying to fix involves things like the following:
#
# *((some_type *) ptr)++;
#
# The desired behavior in this case is to increment the pointer as if it
# were a (some_type *). In some cases, the pointer is typed as void, in
# other cases it has a specific type, but regardless the compiler needs to
# treat it as a different type when it is incrementing it.

You can get a lvalue by judicious use of & and *.

lvalue ref char s
ref ref char (&s)
ref ref int (int**)(&s)
lvalue ref int *((int**)(&s))

(cd /tmp
rm a.out
cat >x.c <<':eof'
#include <stdio.h>
#include <stdlib.h>

#define lvaluecast(type,lvalue) (*((type*)((void*)(&(lvalue)))))

int main(int N,char **P) {
char *s = malloc(27),*s0; int v;
strcpy(s,"abcdefghijklmnopqrstuvwxyz");
s0 = s;
v = *lvaluecast(int*,s)++;
printf("s0=%p s=%p %d v=%x\n",s0,s,s-s0,v);
return 0;
}
:eof
cc x.c
a.out)

s0=0x300140 s=0x300144 4 v=61626364

--
SM Ryan http://www.rawbw.com/~wyrmwif/
Raining down sulphur is like an endurance trial, man. Genocide is the
most exhausting activity one can engage in. Next to soccer.
Nov 14 '05 #7

P: n/a

On Tue, 24 Aug 2004, SM Ryan wrote:

[Someone else wrote, I assume:]
# Much of the code I'm trying to fix involves things like the following:
#
# *((some_type *) ptr)++;
#
# The desired behavior in this case is to increment the pointer as if it
# were a (some_type *). In some cases, the pointer is typed as void, in
# other cases it has a specific type, but regardless the compiler needs to
# treat it as a different type when it is incrementing it.
SM Ryan: Please don't use # as a "quoting" mechanism, because it's
annoying, it's hard to read and it breaks many line-wrapping programs.
(Also, please endeavor not to tell untruths to newbies, but I'll give
you the benefit of the doubt this time. See below.)
You can get a lvalue by judicious use of & and *.

lvalue ref char s
ref ref char (&s)
ref ref int (int**)(&s)
lvalue ref int *((int**)(&s))
Undefined behavior. You can tell it's suspect without even knowing
the Standard, because I could drop one line in your "derivation" and come
up with the same answer where it's more obviously incorrect.

lvalue ref char s
ref ref int (int**)s
lvalue ref int *(int**)s

(cd /tmp
rm a.out
cat >x.c <<':eof'
#include <stdio.h>
#include <stdlib.h>

#define lvaluecast(type,lvalue) (*((type*)((void*)(&(lvalue)))))
Overly complicated
#define lvaluecast(type,lvalue) (*(type*)&(lvalue))
and insufficiently general
lvaluecast(void(*)(), fnptr) = bar;
and flat-out wrong.
assert(sizeof(char) != sizeof(int));
lvaluecast(int, mychar[0]) = 255;
printf("%d\n", mychar[1]);
int main(int N,char **P) {
char *s = malloc(27),*s0; int v;
strcpy(s,"abcdefghijklmnopqrstuvwxyz");
s0 = s;
v = *lvaluecast(int*,s)++;
Here is the crash. If sizeof(char*)!=sizeof(int*), you are
overwriting memory you don't own. If the layouts of the two
types are different, you are causing serious Bad Stuff. No
matter what, this code is dangerous.
printf("s0=%p s=%p %d v=%x\n",s0,s,s-s0,v);
And just for the record:
printf("s0=%p s=%p %d v=%x\n",(void*)s0,(void*)s,s-s0,(unsigned)v);
although some people think the first two casts aren't needed when 's0'
and 's' are pointers to 'char'. I put them in anyway.
return 0;
}


-Arthur

Nov 14 '05 #8

P: n/a
In Late August SM Ryan provided the answer I was looking for.

His lvaluecast macro did what I needed. It worked in C++
M$VC ver 6 which is what I was using when I needed it.

A secondary thanks goes to supernews for their long
retention period of this NG.

In part my code now looks like this...

/* Thanks to SM Ryan who provided the
* LV macro in news:comp.lang.c
* As given, the macro name was lvaluecast
* Message-ID: <10*************@corp.supernews.com>
*/

#define LV(type,lvalue) (*((type*)((void*)(&(lvalue)))))

void foo(void* dest) {
*LV(dest,int*)++ = bar();
}

Peter Butler (all email addresses are invalid)
Nov 14 '05 #9

P: n/a
Peter Butler wrote:
In Late August SM Ryan provided the answer I was looking for.
Blind leading the blind..

#define LV(type,lvalue) (*((type*)((void*)(&(lvalue)))))

void foo(void* dest) {
*LV(dest,int*)++ = bar();
}


That expands to:
.. (*((dest*)((void *)(&(int*)))))++ = bar();

which doesn't even compile. Even if you fixed it,
it is dreadful and won't work on most other compilers.
Why you would prefer that to:

.. *(int *)dest = bar();

beats me.

Nov 14 '05 #10

P: n/a
in comp.lang.c i read:
Peter Butler wrote:
In Late August SM Ryan provided the answer I was looking for. #define LV(type,lvalue) (*((type*)((void*)(&(lvalue)))))

void foo(void* dest) {
*LV(dest,int*)++ = bar();
}

[which really should be *LV(int*,dest)++ = bar();]
Why you would prefer that to:

. *(int *)dest = bar();

beats me.


in part because what you wrote doesn't do what is wanted -- increment the
pointer after assigning a value to the object to which dest points, all
using an explicit (and potentially different) type. the example does not
show dest being used further, but it seems reasonable to assume it is
incremented for a reason.

other problems remain, bot with the macro and in your partial replacement:
alignment (of the pointed to object) and representation (of the pointer
value). if the passed pointer is not a pointer to int then the result of
the cast may not be valid, and even if it is valid it may not point to
memory with appropriate alignment. if the user is sufficiently certain
that these are not issues then the macro does what is wanted without
cluttering the code with casts.

i tend to prefer being explicit -- a temporary is not a sin:

void foo(void *dest)
{
int *p = dest; /* dest had better be compatible with int* */
*p++ = bar(); /* *dest must have alignment compatible with int */
}

--
a signature
Nov 14 '05 #11

P: n/a
those who know me have no need of my name wrote:
.... snip ...
void foo(void *dest)
{
int *p = dest; /* dest had better be compatible with int* */
*p++ = bar(); /* *dest must have alignment compatible with int */
}


What possible influence on the outside world can the ++ have?

Without it we have:

int *p = dest;
*p = bar();

and both statements are necessary. C syntax is a weird and
wonderful thing. To avoid such apparent anomalies I recommend:

int *p;

p = dest;
*p = bar();

(which probably generates the identical code, but is readable)

--
"If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers." - Keith Thompson
Nov 14 '05 #12

P: n/a
in comp.lang.c i read:
those who know me have no need of my name wrote:

void foo(void *dest)
{
int *p = dest; /* dest had better be compatible with int* */
*p++ = bar(); /* *dest must have alignment compatible with int */
}


What possible influence on the outside world can the ++ have?


none at all. did you miss the part where i commented that the original
poster used the operator so it shouldn't be ignored? i suppose i should
have included yet another comment, just in case.

--
a signature
Nov 14 '05 #13

P: n/a
those who know me have no need of my name wrote:
in comp.lang.c i read:
Peter Butler wrote:
#define LV(type,lvalue) (*((type*)((void*)(&(lvalue)))))

void foo(void* dest) {
*LV(dest,int*)++ = bar();
}


[which really should be *LV(int*,dest)++ = bar();]
Why you would prefer that to:

. *(int *)dest = bar();

beats me.


in part because what you wrote doesn't do what is wanted --
increment the pointer after assigning a value to the object
to which dest points,
the example does not show dest being used further, but it seems
reasonable to assume it is incremented for a reason.


Well, I'm not even sure what he is trying to increment. If we
take the expansion of *LV(int*,dest)++ = bar() as given:

.. * (*((int *)((void *)(&dest)))) ++ = bar();

Firstly, I don't see the point of the (void *) bit. The
code would work either the same or better without it.
So I will remove it for clarity, if that's OK. We have:

.. *(*(int *)&dest)++ = bar();

which doesn't even compile because you can't dereference an int.
Suppose he meant "LV(....)++" without the dereference.
We have:

.. (*(int *)&dest)++ = bar();

which compiles but has undefined behaviour because *(int*)&dest
is written to twice without an intervening sequence point.

Yet another alternative would be if LV was meant to be:

.. #define LV(type,lvalue) ((type*)((void*)(&(lvalue))))

giving us:

.. * ((int *)&dest)++ = bar();

This increments the rvalue (int *)&dest, which is not permitted
in ANSI C. However some compilers offer this as an extension
(which would actually change the value of 'dest' to the desired
value). But, the original context of this thread was that the
OP wanted a macro which would simulate this extension on
a compiler that did NOT offer it. So this code is, yet again,
invalid.

AFAIK this leaves us still with no code for LV() that would
work on GCC 3.4. (I could think of such a macro that would
work, but it is notably more complicated than the OP's
macro).
other problems remain, bot with the macro and in your partial
replacement: alignment (of the pointed to object) and
representation (of the pointer value). if the passed pointer
is not a pointer to int then the result of the cast may not be
valid, and even if it is valid it may not point to memory with
appropriate alignment.
Well yes.
if the user is sufficiently certain that these are not issues
then the macro does what is wanted without cluttering the code
with casts.
To someone unfamiliar with this LV macro, it is immediately
apparent that *(int *)p may have alignment and validity issues,
and it is immediately clear what the purpose of the construct is.
In other words, it is easier to maintain.
Also, it is fewer characters than the LV version, so it hardly
qualifies as "cluttering the code".

The only place I'd condone this macro to any degree, is when
porting some code from a cast-as-lvalue compiler, to a
different one, and you didn't know what you were doing, so
you needed the macro so that you could edit it until it worked.
But this is clutching at straws.
i tend to prefer being explicit -- a temporary is not a sin:

void foo(void *dest)
{
int *p = dest; /* dest had better be compatible with int* */
*p++ = bar(); /* *dest must have alignment compatible with int */ }


That neither increments dest, nor (int *)&dest.
I guess you want to add a "dest = p" later (assuming that dest
is subsequently used again, and that the intention is to
increment dest, rather than *dest, as discussed above).

Nov 14 '05 #14

P: n/a
[I am going to snip all of the background.]

In article <11*********************@c13g2000cwb.googlegroups. com>
Old Wolf <ol*****@inspire.net.nz> wrote:
... I'm not even sure what he is trying to increment. ...


Almost invariably, when people try to write these weird things with
casts, what they *really* have is a buffer, with a pointer --
usually of type "char *" or "unsigned char *", but sometimes some
other type -- pointing into the buffer, and they are doing the
equivalent of marshalling or unmarshalling parameters for a remote
procedure call. (This also covers writing data structures to
files.)

For instance:

unsigned char buf[SOME_SIZE];
unsigned char *cp = &buf[0];
...
(code that sets up various values of various types)

At this point, the user wants to copy the bitwise representation of
each value into the buffer. A clear, simple, and correct way to
do this is:

memcpy(cp, &var1, sizeof var1);
cp += sizeof var1;

memcpy(cp, &var2, sizeof var2);
cp += sizeof var2;

memcpy(cp, &var3, sizeof var3);
cp += sizeof var3;

/* and so on */

Note that this works no matter what the type of the various "var"s
is, as long as they all fit into the provided buffer.

What they write instead, thinking this is somehow "better", is:

*((short *)cp)++ = var1; /* assuming var1 is a short */
*((double *)cp)++ = var2; /* assuming var2 is a double */
*((int *)cp)++ = var3; /* assuming var3 is an int */

Of course, this fails to compile cleanly in any correct C compiler,
because the result of a cast is not an lvalue. Having converted
"cp" from "unsigned char *" to "short *", it *might* be OK to do:

*(short *)cp = var1;

but they are also trying to "postfix ++ update" cp by sizeof(short)
in the same C expression, "because it is so much more efficient
that way".

Of course, it might *not* be OK, and it often is no more efficient
after all. If we write it as valid (but maybe-not-OK) C code:

*(short *)cp = var1;
cp += sizeof(short);

*(double *)cp = var2;
cp += sizeof(double);

*(int *)cp = var3;
cp += sizeof(int);

the potential problem is reasonably apparent: there is no guarantee
that "cp" is properly aligned, and it is actually quite likely that
*(double *)cp, at least, is NOT properly aligned. (This code will
usually fail on a SPARC or MIPS machine, for instance. There is a
small chance -- 1 out of 8 if "buf" is aligned randomly; in practice,
much less than 1 out of 8 -- that it will work.)

Perhaps amusingly, even if you do manage to "trick" the machine
into generating appropriate (for that machine) code to store the
three values given here, and update "cp" by the appropriate size,
all in one line, the resulting machine code (on the SPARC or MIPS
at least, and typically on the i386 as well) can be much worse
than if you just use the separate "cp +=" lines:

unsigned char *f2(unsigned char *cp) {
*(*(short **)&cp)++ = var1;
*(*(double **)&cp)++ = var2;
*(*(int **)&cp)++ = var3;

return cp;
}

Many C compilers will be unable to put "cp" in a register here,
so that you would get SPARC code of the form:

st %i0, [%fp - 4] ! copy "cp" parameter to memory

sethi %hi(var1), %g1 ! copy var1 to %g1 reg
ldh [%g1 + %lo(var1)], %g1
ld [%fp - 4], %l0 ! get *(short **)&cp
sth %g1, [%l0] ! store var1 at *(that)
add %l0, 2, %l0 ! increment by 2
st %l0, [%fp - 4] ! and update *(short **)&cp

sethi %hi(var2), %g1 ! copy var2 to %g1
ldd [%g1 + %lo(var1)], %o2
ld [%fp - 4], %l0 ! get *(double **)&cp
std %o2, [%l0] ! store var2 at *(that)
add %l0, 8, %l0 ! increment by 8
st %l0, [%fp - 4] ! and update *(double **)&cp

[repeat for var3, using "ld/st" instead of ldh/sth / ldd/std]

ld [%fp - 4], %i0 ! set return value

/* actually we could probably do this whole thing as a leaf */

ret; restore

Use the simpler (but two lines per store) version:

unsigned char *f3(unsigned char *cp) {
*(short *)cp = var1;
cp += sizeof(short);

*(double *)cp = var2;
cp += sizeof(double);

*(int *)cp = var3;
cp += sizeof(int);

return cp;
}

will definitely give you a leaf routine, and considerably shorter
machine code, even if the compiler is not all that clever:

sethi %hi(var1), %g1 ! copy var1 to %g1
ldh [%g1 + %lo(var1)], %g1
sth %g1, [%o0] ! store at *(short *)cp
add %o0, 2, %o0 ! cp += 2

sethi %hi(var2), %g1 ! copy var2 to %g1
ldd [%g1 + %lo(var1)], %o2
std %o2, [%o0] ! store at *(double *)cp
add %o0, 8, %o0 ! cp += 8

[repeat for var3]

/* delay slot will probably be filled by last "add", actually */
retl; nop

Of course, even this shorter version still generally fails at runtime,
because "*(type *)cp" is not aligned properly for the given type.

The memcpy() version always works. Therefore, people do not write
that one. :-)
--
In-Real-Life: Chris Torek, Wind River Systems
Salt Lake City, UT, USA (4039.22'N, 11150.29'W) +1 801 277 2603
email: forget about it http://web.torek.net/torek/index.html
Reading email is like searching for food in the garbage, thanks to spammers.
Nov 14 '05 #15

P: n/a
On Thu, 27 Jan 2005 13:21:13 -0800, Old Wolf wrote:

....
Suppose he meant "LV(....)++" without the dereference.
We have:

. (*(int *)&dest)++ = bar();

which compiles but has undefined behaviour because *(int*)&dest
is written to twice without an intervening sequence point.


The result of ++ isn't an lvalue so you can't assign to it.

Lawrence
Nov 14 '05 #16

This discussion thread is closed

Replies have been disabled for this discussion.