Expressions "j = ++(i | i); and j = ++(i & i); should be a lvalue error?
Asked Answered
W

5

45

I was expecting that in my following code:

#include<stdio.h> 
int main(){
    int i = 10; 
    int j = 10;

    j = ++(i | i);
    printf("%d %d\n", j, i);

    j = ++(i & i);
    printf("%d %d\n", j, i);

    return 1;
}

expressions j = ++(i | i); and j = ++(i & i); will produce lvalue errors as below:

x.c: In function ‘main’:
x.c:6: error: lvalue required as increment operand
x.c:9: error: lvalue required as increment operand   

But I surprised that above code compiled successfully, as below:

~$ gcc x.c -Wall
~$ ./a.out 
11 11
12 12   

Check the above code working correctly.

While other operators produce error (as I understand). Even bitwise operator XOR causes of an error j = ++(i ^ i); (check other operators produce an lvalue error at compilation time).

What is the reason? Is this is unspecified or undefined ? or bitwise OR AND operators are different?

compiler version:

gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)

But I believe compiler version shouldn't reason for non-uniform behavior. If ^ not compiled then | and & also not. otherwise should work for all

Its not an error with this compiler in c99 mode: gcc x.c -Wall -std=c99.

Westerfield answered 13/2, 2013 at 18:13 Comment(22)
my question is why compiled ?Westerfield
Don't understand your question. Why would it fail? It is perfectly right.Impassible
It fails on my machine (GCC 4.7). Which GCC are you using?Cheeseburger
Fails for me on both clang and gcc.Vaquero
It works with GCC 4.4 for me... It seems to be a corrected bug.Audry
Maybe I experienced a syntax error (perhaps even a semantic error) parsing the above text. It does not compile here (gcc-4.6.1) and it should not compile.Dewie
@texasbruce it should not compiled be cause (i|i) is expression not a variable see ++(i|i) something like (i|i) = (i|i)` + 1` that is lvalue error.Westerfield
What is the specific compiler version you are using? I've tried two different gcc releases, clang, and intel. All emit a compiler diagnostic.Unruly
@GrijeshChauhan Yeah you are right. Just realized.Impassible
@Unruly Just mention in question but But I believe compiler version shouldn't reason for non-uniform behavior. If ^ not compiled then | and & also not. otherwise true for all.Westerfield
The reason we're asking for the specific compiler version is, ++(i|i) and ++(i&i) should trigger semantic errors, so this is a bug in whatever compiler you're using. Bugs do not have to behave uniformly.Gild
Looks like you are using an old and busted compiler. Who cares why it's not broken in a consistent manner. Upgrade your tools and move on.Unruly
@Unruly May be. Are your sure?Westerfield
The behavior you observe doesn't occur in newer gcc releases or other compilers. Why are you having a difficult time understanding this?Unruly
I can't quote chapter and verse at you from this computer (which doesn't have a copy of C99 on it) but I do know for sure that your code should not compile and that this indicates a bug in the compiler you are using.Gild
@Unruly It is conceivable that the OP's specific version of gcc is correct and everyone else is wrong. It's extraordinarily unlikely in this case, but it's still true that "should this compile" questions can only be answered by reference to the language standard, not the behavior of compilers. (Today I am the kind of pedant who insists on a difference between "this is supposed to be an invalid program" and "this isn't technically an invalid program but good luck finding a compiler that supports it".)Gild
Looks like this is a problem with GCC 4.4.x (I see the problem with 4.4.3) - I'm not sure if earlier releases also have the problem or if it was introduced in 4.4. It's clearly been fixed in more recent versions of GCC.Assignor
@Unruly see I have knowledge for compiler writing so my concept says that if its false(or bug) it should be for all. other wise something far my understanding. And I feels Mr. abelenky has answered.Westerfield
@GrijeshChauhan Ah, I see where your confusion is coming from. Please read through the function at gcc.gnu.org/viewcvs/trunk/gcc/… -- you don't have to understand the code in detail, in fact you can probably just pay attention to the comments. This should demonstrate to you that production compilers contain enormous piles of ad-hoc heuristics which do not necessarily apply consistently to all related language constructs.Gild
@Zack Thanks a lot Zack. I am reading it. Will report back if you find some answer. ThanksWesterfield
@GrijeshChauhan If you think you need to spend more than a couple minutes looking at that code you have misunderstood the point I was trying to make by pointing you at it.Gild
@Zack Thanks Zack you for your answer and GCC link. Check my answer too.Westerfield
D
28

You are right that it should not compile, and on most compilers, it does not compile.
(Please specify exactly which compiler/version is NOT giving you a compiler error)

I can only hypothesize that the compiler knows the identities that (i | i) == i and (i & i) == i and is using those identities to optimize away the expression, just leaving behind the variable i.

This is just a guess, but it makes a lot of sense to me.

Dyun answered 13/2, 2013 at 18:26 Comment(2)
From the OP: gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5).Adoration
Note that i^i simplifies to 0, so there's nothing to incrementGranth
C
25

This is a bug that has been addressed in more recent GCC versions.

It's probably because the compiler optimizes i & i to i and i | i to i. This also explains why the xor operator didn't work; i ^ i would be optimized to 0, which is not a modifiable lvalue.

Cheeseburger answered 13/2, 2013 at 18:28 Comment(3)
@Antonijn thanks Antonijn good answer. And quite similar to abelenky's awnser. I accepted older one. Thanks!Westerfield
Are you aware of a bug report or commit that you could link to?Disjoint
@RichardHansen No, but from tests I ran myself I could conclude that in the GCC version I used at the time (possible 4.6, not sure), the bug had been fixed.Cheeseburger
A
17

C11 (n1570), § 6.5.3.1 Prefix increment and decrement operators
The operand of the prefix increment or decrement operator shall have atomic, qualified, or unqualified real or pointer type, and shall be a modifiable lvalue.

C11 (n1570), § 6.3.2.1 Lvalues, arrays, and function designators
A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const- qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const- qualified type.

C11 (n1570), § 6.3.2.1 Lvalues, arrays, and function designators
An lvalue is an expression (with an object type other than void) that potentially designates an object.

C11 (n1570), § 3. Terms, definitions, and symbols
Object: Region of data storage in the execution environment, the contents of which can represent values

As far as I know, potentially means "capable of being but not yet in existence". But (i | i) is not capable of referencing a region a data storage in the execution environment. Therefore it is not an lvalue. This seems to be a bug in an old gcc version, fixed since. Update your compiler!

Audry answered 13/2, 2013 at 18:33 Comment(4)
Thanks for digging up standardese, but could you please also quote the definition of "potentially designates an object" and the constraints for the ++ operator (which is where it'll say that the operand has to be an lvalue)? Just to eliminate all doubt.Gild
There should be language somewhere near 6.3.2.1 that elaborates on exactly what expressions can "potentially designate an object", that's what I really want to see here.Gild
@Zack I'd think that's to exclude literals from the definition of object. For example, the literal "hello" is an lvalue and of type object but not an object. Hence, the cautionary words "potentially an object*. But I don't know if there's an explicit defintion of it in the standard.Keeney
@Kirilenko Hi Kirilenko! you answer was quite helpful to me :) thanksWesterfield
W
7

Just a follow-up to my question . I added elaborate answer so that one can find it helpful.

In my code expressions j = ++(i | i); and j = ++(i & i); are not caused for lvalue error ?

Because of compiler optimization as @abelenky answered (i | i) == i and (i & i) == i. That is exactly CORRECT.

In my compiler (gcc version 4.4.5), any expression that includes single variable and result is unchanged; optimized into a single variable (something called not an expression).

for example:

j = i | i      ==> j = i
j = i & i      ==> j = i
j = i * 1      ==> j = i
j = i - i + i  ==> j = i 

==> means optimized to

To observe it I written a small C code and disassemble that with gcc -S.

C-Code: (read comments)

#include<stdio.h>
int main(){
    int i = 10; 
    int j = 10;
    j = i | i;      //==> j = i
        printf("%d %d", j, i);
    j = i & i;      //==> j = i
        printf("%d %d", j, i);
    j = i * 1;      //==> j = i
    printf("%d %d", j, i);
    j = i - i + i;  //==> j = i
    printf("%d %d", j, i);
}

assembly output: (read comments)

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $10, 28(%esp)   // i 
    movl    $10, 24(%esp)   // j

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf  

In above assembly code all expressions converted into following code:

movl    28(%esp), %eax  
movl    %eax, 24(%esp)

that is equivalent to j = i in C code. Thus j = ++(i | i); and and j = ++(i & i); are optimized to j = ++i.

Notice: j = (i | i) is a statement where as expression (i | i) not a statement (nop) in C

Hence my code could successfully compiled.

Why j = ++(i ^ i); or j = ++(i * i); , j = ++(i | k); produce lvalue error on my compiler?

Because either expression has the constant value or not modifiable lvalue (unoptimized expression).

we can observe using asm code

#include<stdio.h> 
int main(){
    int i = 10; 
    int j = 10;
    j = i ^ i;
    printf("%d %d\n", j, i);
    j = i - i;
    printf("%d %d\n", j, i);
    j =  i * i;
    printf("%d %d\n", j, i);
    j =  i + i;
    printf("%d %d\n", j, i);        
    return 1;
}

assembly code: (read comments)

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $10, 28(%esp)      // i
    movl    $10, 24(%esp)      // j

    movl    $0, 24(%esp)       // j = i ^ i;
                               // optimized expression i^i = 0
    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    $0, 24(%esp)      //j = i - i;
                              // optimized expression i - i = 0
    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax    //j =  i * i;
    imull   28(%esp), %eax
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax   // j =  i + i;
    addl    %eax, %eax
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    $1, %eax
    leave

Hence so this produce an lvalue error because operand is not a modifiable lvalue. And non-uniform behavior is due to compiler optimization in gcc-4.4.

Why new gcc compliers(or most of compilers) produce an lvalue error?

Because evaluation of expression ++(i | i) and ++(i & i) prohibits actual defination of increment(++) operator.

According to Dennis M. Ritchie's book "The C Programming Language" in section "2.8 Increment and Decrement Operators" page 44.

The increment and decrement operators can only be applied to variables; an expression like (i+j)++ is illegal. The operand must be a modifiable lvalue of arithmetic or pointer type.

I tested on new gcc compiler 4.47 here it produces error as I was expecting. I also tested on tcc compiler.

Any feedback/ comments on this would be great.

Westerfield answered 14/2, 2013 at 16:38 Comment(0)
B
1

I don't think at all it is an optimization error, because if it was, then there should not be any error in the first place. If ++(i | i) is optimized to ++(i), then there should not be any error, because (i) is an lvalue.

IMHO, I think that the compiler sees (i | i) as an expression output, that, obviously, outputs rvalue, but the increment operator ++ expects an lvalue to change it, thus the error.

Brubeck answered 16/3, 2013 at 14:34 Comment(3)
Ghasan ++(i) is not lvalue. Thanks :)Westerfield
@GrijeshChauhan I didn't say ++(i) is an lvalue, but (i). That's why claiming that the compiler optimizes the expression from (i | i) to (i) is not true, because ++(i) is valid.Illa
Ghasan Sorry I misunderstood you. Ok read my answer. What I shown that.. in GCC 4.4.5 ++(i|i) is optimized to ++(i) that is the reason code is working on 4.4.5 But evaluation of expression ++(i|i) is against the definition of ++ operator in C So later in new version of GCC the ++(i|i) produce an lvalue error. Only.Westerfield

© 2022 - 2024 — McMap. All rights reserved.