Casts on lvalues

B

BartC

Suppose I have these types:

#define byte unsigned char

typedef struct {
int a,b,c,d;
} R; /* assume this is 16 bytes */

And these variables:

int n; /* represents a *byte* offset */
R* p;

I want to be able to do the following:
p += n;

but it doesn't work because n is a byte offset; it's not in terms of R
objects. But the obvious cast:

(byte*)p+=n;

doesn't appear to compile. The workarounds seem to be:

p += n/sizeof(R);

which I don't like. Even though p is always aligned (and n is known to be a
multiple of 16), and I know the divide will cancel out, it seems funny
having to introduce a divide op in the first place. And:

p = (R*)((byte*)p+n);

which is what I'm using but looks very untidy in real code.

In any case, the question remains, *is* there a way to cast an lvalue as
I've shown above?
 
S

SG

Am 02.12.2012 14:23, schrieb BartC:
Suppose I have these types:

#define byte unsigned char

typedef struct {
int a,b,c,d;
} R; /* assume this is 16 bytes */

And these variables:

int n; /* represents a *byte* offset */
R* p;

I want to be able to do the following:
p += n;

but it doesn't work because n is a byte offset; it's not in terms of R
objects. But the obvious cast:

(byte*)p+=n;

doesn't appear to compile. The workarounds seem to be:

p += n/sizeof(R);

which I don't like. Even though p is always aligned (and n is known to be a
multiple of 16), and I know the divide will cancel out, it seems funny
having to introduce a divide op in the first place. And:

p = (R*)((byte*)p+n);

which is what I'm using but looks very untidy in real code.

In any case, the question remains, *is* there a way to cast an lvalue as
I've shown above?

In C++ there is. But you would invoke undefined behaviour because doing
it in this case violates the aliasing rule. I really don't see the
problem with

p += n/sizeof(R);

It looks like exactly the thing you should write. Maybe throw an

assert(n % sizeof(R) == 0);

in there as well.
 
B

Ben Bacarisse

BartC said:
Suppose I have these types:

#define byte unsigned char

typedef struct {
int a,b,c,d;
} R; /* assume this is 16 bytes */

And these variables:

int n; /* represents a *byte* offset */
R* p;

I want to be able to do the following:
p += n;

but it doesn't work because n is a byte offset; it's not in terms of R
objects. But the obvious cast:

(byte*)p+=n;

doesn't appear to compile. The workarounds seem to be:

p += n/sizeof(R);

which I don't like. Even though p is always aligned (and n is known to be a
multiple of 16), and I know the divide will cancel out, it seems funny
having to introduce a divide op in the first place. And:

p = (R*)((byte*)p+n);

which is what I'm using but looks very untidy in real code.

In any case, the question remains, *is* there a way to cast an lvalue as
I've shown above?

No, I don't think so. Certainly not directly -- a cast expression is
not a lvalue. You can do dangerous thing like:

*(byte **)&p += n; /* don't do this!! */

or use a union with an R * and a char * pointer in it, but both
techniques rely on the representation of char and struct pointers being
the same -- they re-interpret the pointer rather than converting it.

I think your best bet is to tidy up what you currently use

static inline void *addr_inc(void *p, int n) { return (char *)p + n; }

will let you write p = addr_inc(p, n); which is much less messy and not
hard to follow.
 
K

Keith Thompson

BartC said:
Suppose I have these types:

#define byte unsigned char

Why are you using a macro rather than a typedef?
typedef struct {
int a,b,c,d;
} R; /* assume this is 16 bytes */

Or you could assume that it's `sizeof R` bytes.
And these variables:

int n; /* represents a *byte* offset */
R* p;

I want to be able to do the following:
p += n;

but it doesn't work because n is a byte offset; it's not in terms of R
objects. But the obvious cast:

(byte*)p+=n;

doesn't appear to compile.

Right. A cast operator doesn't require an lvalue as its operand.
If the operand happens to be an lvalue, it's not treated as one.
It undergoes an "lvalue conversion" as described in N1570 6.3.2.1p2.
The result of a cast is not an lvalue.
The workarounds seem to be:

p += n/sizeof(R);

which I don't like. Even though p is always aligned (and n is known to be a
multiple of 16), and I know the divide will cancel out, it seems funny
having to introduce a divide op in the first place. And:

p = (R*)((byte*)p+n);

which is what I'm using but looks very untidy in real code.

In any case, the question remains, *is* there a way to cast an lvalue as
I've shown above?

No.

Why is n a byte offset in the first place? You're dealing with
objects of your anonymous struct type; can't you just give your
struct a name and make p a pointer to it? If you need a byte
pointer, you can convert a struct pointer to byte*.

Incidentally, I'd probably use unsigned char directly; it's clear enough
and doesn't make the reader wonder how "byte" has been defined.
 
B

BartC

Why is n a byte offset in the first place?

The offsets come from externally generated data, and initially were simple
counts: +2, -1 etc. The code looked like this:

R *p,*q;

p=q+n;

Very nice. Then I noticed this addition involved internally multiplying n by
16 (or a shift as it was), which wasn't so nice! It was just as easy to
generate these numbers as multiples of 16 anyway (+32, -16 etc) so I did
that. But then the code wasn't so pretty.
 
E

Eric Sosman

I don't think that the standard explicitly states this;

It's in a footnote to 6.5.4p5. Footnotes are non-normative,
but then there's the definition of lvalue in 6.3.2.1p1:

"An lvalue is an expression [...] that potentially
designates an object [...]"

There is no cast that produces an object designator,[*] hence
there is no cast that produces an lvalue.

[*] You can (sometimes) derive an lvalue from the result
of a cast by applying an operator to it, as in

* (unsigned char*)&foo = 42;

or
((struct baz*)&foo) -> mumble = 17;
but as far as I can tell,
the result of any conversion of the type of any expression,
is not an lvalue.

Even a non-conversion is not an lvalue:

double trouble;
(double)trouble = 3.14; // BZZZT!
 
J

James Kuyper

I don't think that the standard explicitly states this;
but as far as I can tell,
the result of any conversion of the type of any expression,
is not an lvalue.

The standard says that something is not an lvalue in only a few places:
6.3.2.1p3 when an lvalue of array type is converted to a pointer to its
first element.
6.5.2.3p3 function_returning_struct_type().member
6.5.16p3 assignment expression

Most of the time, it only says when something IS an lvalue:
6.5.1p2 object_identifier
6.5.1p4 "string literal"
6.5.1p5 (lvalue)
6.5.1.1p4 _Generic(x, int:lvalue1, default:lvalue2)
6.5.2.3p3 lvalue.member
6.5.2.3p4 expression->member
6.5.2.5p4 compound literal
6.5.3.4p4 *pointer_to_object
6.9.1p9 function parameter identifier
 
E

Edward A. Falk

I want to be able to do the following:
p += n;

but it doesn't work because n is a byte offset; it's not in terms of R
objects. But the obvious cast:

(byte*)p+=n;

doesn't appear to compile. The workarounds seem to be:

p += n/sizeof(R);

Other options:

p += n/sizeof(*p);

p = (R *)((byte *)p +n)

But frankly, none of these is a very good choice. If I were
code-reviewing something like this, I'd ask the programmer what it is
that they're *really* trying to do. Constructs like this are
a likely sign of broken logic and broken code.

Note also that the n/sizeof(...) variant fails horribly if the
byte offset isn't a multiple of the structure size.

Not to say that something like this should *never* be written --
I can think of a number of cases where multiple data structures
might be packed into a buffer, in which case this is exactly the
sort of thing you might be doing. But in cases like this, you're
best off keeping p as a byte pointer and just casting it to a
pointer to R when you need to dereference it.

I'm assuming you're taking the proper precautions w.r.t. alignment
issues. That's a whole 'nother discussion.
 
B

BartC

Other options:

p += n/sizeof(*p);

p = (R *)((byte *)p +n)

But frankly, none of these is a very good choice. If I were
code-reviewing something like this, I'd ask the programmer what it is
that they're *really* trying to do. Constructs like this are
a likely sign of broken logic and broken code.

I've already said elsewhere that using an object rather than a byte offset
involved an unnecessary multiplication or shift.

I noticed it when I wanted shadow some functions with assembly code.
 
P

Piotr Kalinowski

BartC said:
I've already said elsewhere that using an object rather than a byte
offset involved an unnecessary multiplication or shift.

It was not unnecessary. It's called pointer arithmetic and allowed you
to use more elegant code at slightly higher level of abstraction that
is supposed to more closely represent your intentions.

You have decided this abstraction is unnecessary, because you feel you
need to optimise this shift/multiplication away (and maybe you do, I
couldn't possibly know that). More specifically, you have implicitly
decided this optimisation is worth the trade off in form of increased
complexity of the code (decreased readability or whatever it is that
bugged you enough to actually ask about it, seeking a different way to
express what you are doing).

There's no free lunch. Now deal with the consequences, or revert your
decision, as others already presented answer to your original question.

Regards,
Piotr Kalinowski
 
B

BartC

Piotr Kalinowski said:
It was not unnecessary. It's called pointer arithmetic and allowed you
to use more elegant code at slightly higher level of abstraction that
is supposed to more closely represent your intentions.

Yet, C allows to you to do this - cast the target of a pointer - in an
rvalue expression.
You have decided this abstraction is unnecessary, because you feel you
need to optimise this shift/multiplication away (and maybe you do, I
couldn't possibly know that). More specifically, you have implicitly
decided this optimisation is worth the trade off in form of increased
complexity of the code (decreased readability or whatever it is that
bugged you enough to actually ask about it, seeking a different way to
express what you are doing).

I asked about doing the same on the left side of an assignment.

It's this asymmetry that is the issue.
 
E

Eric Sosman

Yet, C allows to you to do this - cast the target of a pointer - in an
rvalue expression.

No: C allows you to apply a cast operator to a *value* (which
may have been extracted from a pointer's target), not to the target
itself.
[...]
I asked about doing the same on the left side of an assignment.

It's this asymmetry that is the issue.

Um, er, assignment itself is asymmetric ...
 
B

BartC

Eric Sosman said:
No: C allows you to apply a cast operator to a *value* (which
may have been extracted from a pointer's target), not to the target
itself.

Take this example:

typedef unsigned char byte;

int a = 0x12345678;
int* p = &a;

printf( "*p = %X\n", *p);
printf( "*p = %X\n", *(byte*)p);

In the last line, p is made to behave as though it was a byte pointer,
rather than an int pointer. And quite likely, a byte value is requested from
memory rather than an int one. Also, in most cases, the value of the pointer
need not be changed.

The effect is to change the target of the pointer *type*, rather than any
actual value.
[...]
I asked about doing the same on the left side of an assignment.

It's this asymmetry that is the issue.

Um, er, assignment itself is asymmetric ...

Plenty of terms can appear interchangeably on both left and right sides.

But while A can appear on either side, (T)A can't. And there doesn't appear
to be a convincing reason why not.
 
J

James Kuyper

On 12/05/2012 01:39 PM, BartC wrote:
....
....
But while A can appear on either side, (T)A can't. And there doesn't appear
to be a convincing reason why not.

The reason is the same as the one that Keith gave to the similar
question you asked at the start of this thread about compound assignment
expressions: "The result of a cast is not an lvalue." Since it's not an
lvalue, no memory is set aside to store anything in it. It appears that
you want

(T)A op= B

to have the meaning that can currently be expressed in standard C as

A = (T)A op B.

but it's not clear to me what you want

(T)A = B

to mean. Can you express it in terms of standard C, the way that I did
above for op=? Consider:

int a=3;
(double)a = 3.14;

Currently, this is equivalent to

3.0 = 3.14;

and therefore disallowed for the same reason that 3.0 = 3.14 is. What
would you like it to do in a revised version of C?
 
E

Eric Sosman

Take this example:

typedef unsigned char byte;

int a = 0x12345678;
int* p = &a;

printf( "*p = %X\n", *p);
printf( "*p = %X\n", *(byte*)p);

In the last line, p is made to behave as though it was a byte pointer,
rather than an int pointer. And quite likely, a byte value is requested
from memory rather than an int one. Also, in most cases, the value of
the pointer need not be changed.

No: `p' is not "made to behave" like anything other than
what it is. `p' is an identifier, which (in this context) is
seen first as a primary expression (6.5.1p2), and then as an
lvalue (6.3.2.1p1). The object designated by this lvalue is
consulted to obtain a value (6.3.2.1p2); this value points at
the object designated by `a' and has the type `int*'.

The `(byte*)' operator converts the extracted value to the
`(unsigned char*)' type (6.5.4), and this converted value points
at the lowest-addressed constituent byte of the object designated
by `a' (6.3.2.3p7).

The `*' operator uses the `(unsigned char*)' value produced
by the cast to designate the pointed-to byte (6.5.3.2p4), and
this designation is an lvalue (6.3.2.1p1 again), from which is
extracted the value stored in the designated byte (6.3.2.1p2
again), which is a value of type `unsigned char'. The default
argument promotions convert this value to `int' or `unsigned int',
and lo! we've computed an argument for printf().

`p' is unchanged throughout all of this. Its value has not
changed, its behavior has not changed, its nature has not changed.
It has not put on makeup, adopted a foreign accent, or affected a
silly walk. It is still an `int*' aimed at the object designated
by `a'.
The effect is to change the target of the pointer *type*, rather than
any actual value.

No. See above -- or ask "Has the type of `p's target changed?"
Since `p's target is exactly what it was to begin with, and has made
no sorties into strange territory and back again, the answer is "No."
There has been no change, hence "the effect is to change" is wrong.
[...]
I asked about doing the same on the left side of an assignment.

It's this asymmetry that is the issue.

Um, er, assignment itself is asymmetric ...

Plenty of terms can appear interchangeably on both left and right sides.

But while A can appear on either side, (T)A can't. And there doesn't
appear to be a convincing reason why not.

If you're not convinced that an lvalue is different from other
kinds of expressions, you're beyond my power to educate -- but that's
nothing new.
 
G

Greg Martin

Eric Sosman said:
No: C allows you to apply a cast operator to a *value* (which
may have been extracted from a pointer's target), not to the target
itself.

Take this example:

typedef unsigned char byte;

int a = 0x12345678;
int* p = &a;

printf( "*p = %X\n", *p);
printf( "*p = %X\n", *(byte*)p);

In the last line, p is made to behave as though it was a byte pointer,
rather than an int pointer. And quite likely, a byte value is requested
from memory rather than an int one. Also, in most cases, the value of
the pointer need not be changed.

The effect is to change the target of the pointer *type*, rather than
any actual value.
[...]
I asked about doing the same on the left side of an assignment.

It's this asymmetry that is the issue.

Um, er, assignment itself is asymmetric ...

Plenty of terms can appear interchangeably on both left and right sides.

But while A can appear on either side, (T)A can't. And there doesn't
appear to be a convincing reason why not.

Since you aren't actually changing A by (T)A but rather saying treat
it's value like it's a T rather then A's declared type, it doesn't
really make sense. I can't see the value of momentarily treating A like
it's a T just to fool the compiler. Presuming T is larger then A are you
asking the compiler to overwrite the memory adjacent to A? Probably not.
Your probably asking it to cast the value to A's type which is more
logically written A = (A_Type) some_T; // IMO.

Excuse me if I've missed your meaning but I think if you think about
what a cast is you'll see it doesn't apply to lvalues.
 
B

BartC

Greg Martin said:
On 12-12-05 10:39 AM, BartC wrote:

Since you aren't actually changing A by (T)A but rather saying treat it's
value like it's a T rather then A's declared type, it doesn't really make
sense.

Isn't that the point of having casts?
I can't see the value of momentarily treating A like it's a T just to fool
the compiler.

On a platform that you know inside-out, and where the alternative is to do
exactly the same but using assembly code with all of it's disadvantages,
there there is plenty of value in doing it.

When you have a datablock of mixed-type data, and traversing it via a
pointer, then you might expect to switch pointer types all the time, rather
than messing with unions and memcpys.
Presuming T is larger then A are you asking the compiler to overwrite the
memory adjacent to A? Probably not.

Yes. But you'd be sensible enough not to do that.
Your probably asking it to cast the value to A's type which is more
logically written A = (A_Type) some_T; // IMO.

That's not quite the same. See the example I gave in my reply to James.
 
B

Ben Bacarisse

BartC said:
Plenty of terms can appear interchangeably on both left and right sides.

But while A can appear on either side, (T)A can't. And there doesn't
appear to be a convincing reason why not.

B = (T)A; // OK
(T)A = B; // not OK
B = +A; // OK
+A = B; // not OK
B = !A; // OK
!A = B; // not OK
B = A + C; // OK
A + C = B; // not OK

.... and so on. There's a pattern.
 
B

BartC

James Kuyper said:
On 12/05/2012 01:39 PM, BartC wrote:

The reason is the same as the one that Keith gave to the similar
question you asked at the start of this thread about compound assignment
expressions: "The result of a cast is not an lvalue."

OK, I get that now. But that's only because the Book says so.
It appears that you want

(T)A op= B

to have the meaning that can currently be expressed in standard C as

A = (T)A op B.

It's not quite that either, because this might involve unwanted int/float
conversions.
but it's not clear to me what you want

(T)A = B

to mean. Can you express it in terms of standard C, the way that I did
above for op=?

It means: "pretend that A is a variable of type T for this assignment".
Consider:

int a=3;
(double)a = 3.14;

Currently, this is equivalent to

3.0 = 3.14;

and therefore disallowed for the same reason that 3.0 = 3.14 is. What
would you like it to do in a revised version of C?

Well in this case it wouldn't do anything too useful! But turning it around
a little:

double a;

(int)a = 3142;

This just writes the bit-pattern for integer 3142 in (on my machine) the
bottom half of a. But that, I can currently do in C using instead:

*(int*)&a = 3142;

(even though people don't seem to like this either). My original example was
more like this:

int* P;

++(char*)P;

ie. treat P as as a char* pointer (so that on my machine, the value in P
increments by 1 instead of 4).

I used to do this stuff in another language something like this:

double a;
int n;
equivalence(a,n) /* a and n share the same memory location */

n=3142; /* does what I tried to do above */

OK, I understand in C, you have to use unions and memcpys and things. But
that's not always straightforward (for example, 'a' might be an extern
variable, or part of an array).
 
B

BartC

Ben Bacarisse said:
B = (T)A; // OK
(T)A = B; // not OK
B = +A; // OK
+A = B; // not OK
B = !A; // OK
!A = B; // not OK
B = A + C; // OK
A + C = B; // not OK

... and so on. There's a pattern.

OK, I see it. But: the (T)A=B example might be done instead as:

memcpy(&A, &B, sizeof(T));

So it expresses something that could conceivably make sense. Unlike the
other examples that don't!
 

Ask a Question

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

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

Ask a Question

Similar Threads

function casts 27
Casts 81
Lexical Analysis on C++ 1
lvalues and rvalues 127
Union and pointer casts? 13
Pointer casts for OOP 2
Function is not worked in C 2
Fun with casts 1

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top