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

Gri*_*han 44 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;
}
Run Code Online (Sandbox Code Playgroud)

表情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   
Run Code Online (Sandbox Code Playgroud)

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

~$ gcc x.c -Wall
~$ ./a.out 
11 11
12 12   
Run Code Online (Sandbox Code Playgroud)

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

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

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

编译器版本:

gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
Run Code Online (Sandbox Code Playgroud)

但我认为编译器版本不应该导致非均匀行为.如果^没有编译,然后|&还没有.否则应该适用于所有人

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

abe*_*nky 28

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

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

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

  • 注意`i ^ i`简化为'0`,所以没有什么可增加的 (4认同)
  • 从OP:`gcc version 4.4.5(Ubuntu/Linaro 4.4.4-14ubuntu5)`. (3认同)

ant*_*ijn 25

这是一个已在最近的GCC版本中解决的错误.

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


md5*_*md5 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版本中的一个错误,自那以后就修复了.更新你的编译器!

  • 感谢挖掘标准,但是请你引用"可能指定一个对象"的定义和`++`运算符的约束(这就是说操作数必须是左值)?只是为了消除所有疑问. (2认同)

Gri*_*han 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 
Run Code Online (Sandbox Code Playgroud)

==> 手段 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);
}
Run Code Online (Sandbox Code Playgroud)

汇编输出:( 阅读评论)

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  
Run Code Online (Sandbox Code Playgroud)

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

movl    28(%esp), %eax  
movl    %eax, 24(%esp)
Run Code Online (Sandbox Code Playgroud)

这相当于j = iC代码.因此j = ++(i | i);j = ++(i & i);优化j = ++i.

注意: j = (i | i)是一个语句,其中表达式(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;
}
Run Code Online (Sandbox Code Playgroud)

汇编代码:( 阅读评论)

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
Run Code Online (Sandbox Code Playgroud)

因此,这产生了一个lvalue error因为操作数不是可修改的左值.和非均匀的行为是由于GCC-4.4编译器优化.

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

因为表达式的评估++(i | i)++(i & i)禁止增量(++)运算符的实际定义.

根据Dennis M. Ritchie 在"2.8增量和减量运算符"第44页的" C语言编程 " 一书中的说法.

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

我测试了新的gcc编译器4.47,它产生的错误正如我所期待的那样.我还测试了tcc编译器.

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