7

I need some advice with this strange behavior – lets have this code:

int ** p;

This compiles without any trouble:

p++;

But this:

((int**)p)++;

Gives me this error message: “error: lvalue required as increment operand”.

I am casting to p to the type it already is, nothing changes, so what is the problem? This is simplified version of problem I came across, when I was trying to compile one old version of gdb. So I suppose, that this worked and something changed. Any idea what is wrong with the second example?

Grijesh Chauhan
  • 57,103
  • 20
  • 141
  • 208
Pavel
  • 115
  • 5
  • 2
    "nothing changes" - many things change, actually, one of which is that the result of a cast expression is not an lvalue but an rvalue (it doesn't designate storage), so it doesn't make sense to increment it. –  Jan 27 '14 at 16:45
  • 2
    Yep. "If it hurts when you do that, maybe you aren't supposed to do that." – keshlam Jan 27 '14 at 16:47
  • 2
    The result of a cast has never been an lvalue. It would be interesting to see the *exact* code from that old version of gdb; I suspect your simplification has hidden the actual problem. – Keith Thompson Jan 27 '14 at 16:48
  • IIRC some version of VC++ allowed this (probably VC++ 7.1). – Matteo Italia Jan 27 '14 at 16:49
  • I can hardly imagine `gbd` actually used something like this. It is just plain wrong like Carbonic Acid pointed out. – Jori Jan 27 '14 at 16:49
  • @KeithThompson This was a **BUG** in old GCC compilers(and it is not that old GCC support it!) the Bug is due to optimization. Because variable `p` is already of type `int**` so expression `((int**)p)++;` optimized to `p++;` that is a valid instruction. If suppose expression is `((char**)p)++;` then it will be compilation error in **old** GCC compiler SEE: I tried on [**old GCC compiler**](http://codepad.org/cN5QaTYi). I know this as I have explore after disassemble check my [Question & Ans](http://stackoverflow.com/questions/14860189/expressions-j-i-i-and-j-i-i-should-be-a-lvalue-error) – Grijesh Chauhan Jan 28 '14 at 02:47
  • @H2CO3 Tou forgot but you have already [answered](http://stackoverflow.com/a/19677388/1673391) this issue in GCC. – Grijesh Chauhan Jan 28 '14 at 05:03
  • @GrijeshChauhan That's another question (OP there asks why it **does** work whereas it shouldn't). –  Jan 28 '14 at 06:52
  • @H2CO3 ok then I remove duplicate, keeping it linked. – Grijesh Chauhan Jan 28 '14 at 06:57
  • I think it is clear now, thanks to all of you. If you are curious about the gdb, take a look at gdb 5.3, file include/obstack.h and macro obstack_ptr_grow. It contains this: *((void **)__o->next_free)++ = ((void *)datum); – Pavel Jan 29 '14 at 14:14
  • @Pavel, that extra `*` at the front of the line makes all the difference. – Ben Voigt Apr 16 '16 at 02:06

4 Answers4

10

Old versions of gcc support something called "lvalue casts" -- if you cast something that is an lvalue the result is an lvalue and can be treated as such. The main use for it is allowing you to increment a pointer by an amount corresponding to a different size:

int *p;
++(char *)p;  /* increment p by one byte, resulting in an unaligned pointer */

This extension was deprecated some time around gcc v3.0 and removed in gcc v4.0

To do the equivalent thing in more recent versions of gcc, you need do an addition and assignment (instead of an increment) casting the pointer to the type for the addition and back for the assignment:

p = (int *)((char *)p + 1);

Note that trying to dereference the pointer after this is undefined behavior, so don't count on it doing anything useful.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • *'Old versions of gcc support something called "lvalue casts"'*. No, this was a BUG due to optimization indeed! E.g try `++(char *)p;` give you an error while `++(int *)p;` compiles when `p` is of `int*` type. – Grijesh Chauhan Jan 28 '14 at 02:50
  • @GrijeshChauhan: Both work fine on versions of gcc prior to 4.0. Your [link](http://codepad.org/RTTRgCSV) uses gcc 4.1, so is not old enough. – Chris Dodd Jan 28 '14 at 06:18
  • No first one is not error while second one is [Check this](http://codepad.org/cN5QaTYi) -- first works because `++(int*)p;` converted into `++p;` due to optimization Whereas `++(char*)p;` doesn't. I observed the behaviour some time in past. See this `++( i | i)` will be compiles that compiles `++(int*)p;`. – Grijesh Chauhan Jan 28 '14 at 06:25
7

When you typecast an expression, the result of that expression is an rvalue rather than an lvalue. Intuitively, a typecast says "give me the value that this expression would have if it had some other type," so typecasting a variable to its own type still produces an rvalue and not an lvalue. Consequently, it's not legal to apply the ++ operator to the result of a typecast, since ++ requires an lvalue and you're providing an rvalue.

That said, it is in principle possible to redefine the C language so that casting a value to its own type produces an lvalue if the original expression is an lvalue, but for simplicity's and consistency's sake I suppose the language designers didn't do this.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • 3
    The term is "cast", not "typecast". (Typecasting is something that happens to actors.) – Keith Thompson Jan 27 '14 at 16:49
  • 6
    @KeithThompson I've seen the term "typecast" used for this term extensively, with "cast" just being a short term for it. [Wikipedia](http://en.wikipedia.org/wiki/Type_conversion) seems to support this as well. – templatetypedef Jan 27 '14 at 16:52
  • 2
    The term "typecast" appears nowhere in K&R2 or in the ISO C standard (and only once in that Wikipedia article, which goes on to refer to explicit C type conversions as "casts"). – Keith Thompson Jan 27 '14 at 17:01
  • BTW, I just edited that Wikipedia page, but I didn't touch the reference to "typecasting". – Keith Thompson Jan 27 '14 at 17:08
  • @KeithThompson I suspect that in C/C++ parlance, the term is "cast," while in a broader CS context, the term is "typecast." That would explain things. :-) – templatetypedef Jan 27 '14 at 17:34
  • _Wiktionary_ gives 'typecast' as a synonym for 'cast'. It does seem to have been widely used/abused in that way. I have to agree with Keith that it's incorrect (though probably well established by now) usage. – Phil Perry Jan 27 '14 at 18:28
  • We stumble across two fundamental C issues here: (1) Some casts perform an actual value conversion (`float f=1; (int)f;`); others are actually NOPs which just change the type to appease the compiler or produce some specific behavior in an expression (`int *p =0; char *pc = (char *)p;`). The latter ones could *always* stay lvalues if the original expression is one. ... – Peter - Reinstate Monica Sep 28 '22 at 19:51
  • ... (2) C's type concept is not really good which is why it must be amended with additional lvalue and rvalue properties. Algol68, for example, got it right: There is a difference between an `int` (an immediate value) and a `ref int` (a variable, aka a typed, constant memory location). That's much clearer. – Peter - Reinstate Monica Sep 28 '22 at 19:52
5

In C language all conversions (including explicit casts) always produce rvalues. No exceptions. The fact that you are casting it to the same type does not make it exempt from that rule. (Actually, it would be strange to expect it to make such an inconsistent exception.)

In fact, one of fundamental properties of the entire C language is that it always converts lvalues to rvalues in expressions as quickly as possible. Lvalues in C expressions are like 115th element of Mendeleev table: they typically live a very short life, quickly decaying to rvalues. This is a major difference between C and C++, with the latter always attempting to preserve lvalues in expressions as long as possible (although in C++ this specific cast would also produce an rvalue).

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • So if you wanted to cast something and then use it as an lvalue, is there any way? I can think of assigning the rvalue to an appropriately-declared _pointer_ -- is that a good way, and is there a better one? – Phil Perry Jan 27 '14 at 18:31
  • @PhilPerry: Since we do not know what your actual goal is, it is hard to tell you how to better achieve it. You're probably not trying to get an lvalue out of a cast for its own sake; you have some larger purpose. That purpose will not be achieved by casting and expecting to get an lvalue, so rather than continuing down this false path, describe your *real* problem and ask for help in solving it. – Eric Lippert Jan 27 '14 at 19:17
  • 1
    @Phil Perry: Using something as an lvalue of different type is not really a *type conversion* but rather a *raw memory reinterpretation*. In C *raw memory reinterpretation* is implemented by going up one level of indirection (using `&` operator), performing a cast there and then returning back to the original level using `*` operator. In the OP's case it would look as follows: `(*(int ***) &p)++`. Of course, this example has purely academic value, since `p` is already an `int **`. – AnT stands with Russia Jan 27 '14 at 19:18
  • @Eric, I don't have a specific problem at hand to solve. It's just that @AndreyT's answer triggered the thought that at some time in the future I might want to cast, say, a pointer to something else, and use it as an lvalue, and how best can this be done? Say, I had a `void *` array, and I wanted to increment to the next _integer_, or something like that. – Phil Perry Jan 27 '14 at 19:25
  • 1
    @Phil Perry: If you have a `void *p` and you want to shift it to the next `int`, the best way would be to avoid memory reinterpretation altogether and do `p = (int *) p + 1`. With memory reinterpretation it would look as `++(*(int **) &p)`, but it is inferior to the previous one. – AnT stands with Russia Jan 27 '14 at 19:51
5

Why isn't the result of this cast an lvalue?

I draw your attention to section 6.5.4 of the C99 specification, line 4, footnote 86, which states:

A cast does not yield an lvalue.

You have a cast.

The result is not an lvalue.

The ++ operator requires an lvalue.

Therefore your program is an error.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Eric this was a bug in old compiler I added [a comment to question](http://stackoverflow.com/questions/21386485/why-isnt-the-result-of-this-cast-an-lvalue#comment32272667_21386485) – Grijesh Chauhan Jan 28 '14 at 02:59