表达式“j = ++(i | i);和j = ++(i& i);应该是左值误差?

时间:2013-02-13 18:13:23

标签: c optimization gcc pre-increment gcc4.4

我期待以下代码:

#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;
}

表达式j = ++(i | i);j = ++(i & i);将产生左值错误,如下所示:

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

但我对上面的代码编译成功感到惊讶,如下所示:

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

正确检查the above code working

其他运算符产生错误(据我所知)。甚至按位运算符XOR也会导致错误j = ++(i ^ i);(在编译时检查其他operators produce an lvalue error)。

是什么原因?这是未指定还是未定义?或按位OR AND运算符是不同的?

编译器版本:

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

但我认为编译器版本不应该导致非均匀行为。如果^未编译,则|&也不会编译。否则应该适用于所有

在c99模式下,这个编译器不是错误:gcc x.c -Wall -std=c99

5 个答案:

答案 0 :(得分:28)

你是对的,它不应该编译,并且在大多数编译器上,它不会编译 (请准确说明哪个编译器/版本没有给你编译错误)

我只能假设编译器知道(i | i) == i(i & i) == i的身份,并使用这些身份来优化表达式,只留下变量i

这只是猜测,但对我来说很有意义。

答案 1 :(得分:25)

这是最近GCC版本中已经解决的错误。

这可能是因为编译器优化i & iii | ii。这也解释了为什么xor运算符不起作用; i ^ i将优化为0,这不是可修改的左值。

答案 2 :(得分:17)

  

C11(n1570),§6.5.3.1前缀增量和减量运算符
  前缀增量或减量运算符的操作数应具有原子的,合格的,   或不合格的实数或指针类型,并且应为可修改的左值

     

C11(n1570),§6.3.2.1左值,数组和函数指示符
  可修改的左值是一个左值    没有数组类型,没有不完整的类型,没有const-   合格的类型,如果是结构或联合,则没有任何成员(包括,    递归地,所有包含聚合或联合的任何成员或元素)   合格的类型。

     

C11(n1570),§6.3.2.1左值,数组和函数指示符
  左值是一个潜在的表达式(对象类型不是void)   指定一个对象。

     

C11(n1570),§3。术语,定义和符号
  对象:执行环境中的数据存储区域,其内容可以表示   值

据我所知,可能意味着“能够存在但尚未存在”。但是(i | i)无法在执行环境中引用区域作为数据存储。因此它不是左值。这似乎是旧的gcc版本中的一个错误,自那以后就修复了。更新你的编译器!

答案 3 :(得分:7)

只是我的问题的后续行动。我添加了精心的答案,以便人们发现它有用。

  

我的代码表达式j = ++(i | i);j = ++(i & i);不会导致左值错误?

由于编译器优化,@ abelenky回答(i | i) == i(i & i) == i。这完全是正确的。

在我的编译器(gcc version 4.4.5)中,包含单个变量和结果的任何表达式都保持不变;优化为单个变量(称为而不是表达式)。

例如:

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

==>表示optimized to

为了观察它,我写了一个小的C代码并用gcc -S反汇编。

C代码:阅读评论

#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);
}

汇编输出:(阅读评论

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  

在上面的汇编代码中,所有表达式都转换为以下代码:

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

相当于C代码中的j = i。因此j = ++(i | i);j = ++(i & i);已优化为j = ++i

注意: j = (i | i)是一个声明,其中表达式(i | i) not a statement (nop) in C

因此我的代码可以成功编译。

  

为什么j = ++(i ^ i);j = ++(i * i);j = ++(i | k);在我的编译器上产生左值错误?

因为任一表达式具有常量值或不可修改的左值(未优化的表达式)。

我们可以使用asm代码

进行观察
#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;
}

汇编代码:(阅读评论

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

因此,这会生成lvalue error,因为操作数不是可修改的左值。 非统一行为是由于gcc-4.4中的编译器优化。

  

为什么新的gcc编译器(或大多数编译器)会产生左值误差?

因为对表达式++(i | i)++(i & i)的求值禁止实际定义增量(++)运算符。

根据Dennis M. Ritchie在“”2.8增量和减量运算符“第44页中的”The C Programming Language“一书的说法。

  

增量和减量运算符只能应用于变量;像(i + j)++这样的表达式是非法的。操作数必须是算术或指针类型的可修改左值。

我在新版gcc compiler 4.47 here上测试过它会产生错误,正如我所期待的那样。 我也tested on tcc compiler.

对此的任何反馈/评论都会很棒。

答案 4 :(得分:1)

我根本不认为它是一个优化错误,因为如果是,那么首先应该没有任何错误。如果++(i | i)已针对++(i)进行了优化,则不会出现任何错误,因为(i)是左值。

恕我直言,我认为编译器将(i | i)视为表达式输出,显然,输出rvalue,但增量运算符++需要左值来改变它,从而产生错误。