45

我期待在我的以下代码中:

#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   

检查上面的代码是否正常工作。

而其他运营商产生错误(据我了解)。甚至按位运算符 XOR 也会导致错误j = ++(i ^ i);(检查其他运算符在编译时产生左值错误)。

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

编译器版本:

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

但我相信编译器版本不应该导致不统一的行为。如果^没有编译,那么|&没有。否则应该适用于所有人

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

4

5 回答 5

28

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

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

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

于 2013-02-13T18:26:01.827 回答
25

这是一个已在较新的 GCC 版本中解决的错误。

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

于 2013-02-13T18:28:55.320 回答
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 版本中的错误,此后已修复。更新你的编译器!

于 2013-02-13T18:33:42.410 回答
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)

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

注意: j = (i | i)是一个语句,其中 as 表达式(i | i) 不是 C 中的语句 (nop)

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

为什么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 的书“ The C Programming Language ”在“2.8 Increment and Decrement Operators”第 44 页。

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

我在这里测试了新的gcc 编译器 4.47,它产生了我所期望的错误。我还在tcc 编译器上进行了测试。

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

于 2013-02-14T16:38:08.260 回答
1

我根本不认为这是一个优化错误,因为如果是,那么首先就不应该有任何错误。如果++(i | i)优化为++(i),那么应该不会有任何错误,因为(i)是一个左值。

恕我直言,我认为编译器将(i | i)其视为表达式输出,显然,它输出右值,但增量运算符++期望左值来更改它,从而导致错误。

于 2013-03-16T14:34:03.380 回答