【问题标题】:Expressions "j = ++(i | i); and j = ++(i & i); should be a lvalue error?表达式“j = ++(i | i); 和 j = ++(i & i); 应该是左值错误吗?
【发布时间】:2013-01-29 09:22:52
【问题描述】:

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

#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 &amp; 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)

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

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

【问题讨论】:

  • 它适用于我的 GCC 4.4...这似乎是一个更正的错误。
  • @texasbruce 它不应该被编译,因为(i|i) 是表达式而不是变量,请参阅++(i|i) (i|i) = (i|i)` + 1` 之类的左值错误。
  • 您使用的特定编译器版本是什么?我尝试了两个不同的 gcc 版本,clang 和 intel。都发出编译器诊断。
  • 您观察到的行为不会出现在 较新 gcc 版本或其他编译器中。为什么你很难理解这一点?
  • @Blastfurnace 可以想象 OP 的特定版本的 gcc 是正确的,而其他人都是错误的。在这种情况下,这是非常不可能的,但“应该 this compile”问题仍然可以通过参考语言标准来回答,而不是编译器的行为。 (今天我是那种坚持区分“这应该是一个无效程序”和“这在技术上不是一个无效程序但祝你好运找到支持它的编译器”之间的区别的那种书呆子。)

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


【解决方案1】:

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

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

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

【讨论】:

  • 来自 OP:gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
  • 请注意,i^i 简化为 0,因此无需增加任何内容
【解决方案2】:

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

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

【讨论】:

  • @Antonijn 感谢 Antonijn 的好回答。和abelenky 的遮阳篷非常相似。我接受了旧的。谢谢!
  • 您是否知道可以链接到的错误报告或提交?
  • @RichardHansen 不,但是从我自己运行的测试中我可以得出结论,在我当时使用的 GCC 版本中(可能是 4.6,不确定),该错误已得到修复。
【解决方案3】:

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

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

C11 (n1570),第 6.3.2.1 节左值、数组和函数指示符
左值是一个表达式(对象类型不是void),它可能 指定一个对象。

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

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

【讨论】:

  • 感谢您挖掘标准语言,但您能否引用“可能指定一个对象”的定义和++ 运算符的约束(它会说操作数有成为左值)?只是为了消除所有疑虑。
  • 应该有接近 6.3.2.1 的语言详细说明哪些表达式可以“潜在地指定一个对象”,这就是我真正想在这里看到的。
  • @Zack 我认为这是从对象的定义中排除文字。例如,文字 "hello" 是一个左值,属于 type 对象,但不是对象。因此,警示词“可能是一个对象*。但我不知道标准中是否有明确的定义。
  • @Kirilenko 你好,Kirilenko!你的回答对我很有帮助:) 谢谢
【解决方案4】:

只是对我的问题的跟进。我添加了详尽的答案,以便人们发现它很有帮助。

在我的代码表达式中j = ++(i | i);j = ++(i &amp; i); 不是左值错误引起的?

因为@abelenky 回答了(i | i) == i(i &amp; 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 

==&gt; 表示optimized to

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

C 代码:读取 cmets

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

汇编输出:(读取 cmets)

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 &amp; 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;
}

汇编代码:(读取 cmets

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 &amp; i) 的评估禁止实际定义increment(++) 运算符。

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

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

我在新的gcc compiler 4.47 here 上进行了测试,它产生了我所期望的错误。 我也tested on tcc compiler.

任何关于此的反馈/cmets 都会很棒。

【讨论】:

    【解决方案5】:

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

    恕我直言,我认为编译器将(i | i) 视为表达式输出,显然输出右值,但增量运算符++ 需要一个左值来更改它,因此会出现错误。

    【讨论】:

    • Ghasan ++(i) 不是左值。谢谢:)
    • @GrijeshChauhan 我没有说++(i) 是左值,而是(i)。这就是为什么声称编译器将表达式从(i | i) 优化到(i) 是不正确的,因为++(i) 是有效的。
    • Ghasan 对不起,我误解了你。好的,请阅读我的答案。我所展示的.. 在GCC 4.4.5 ++(i|i) 中优化为++(i) 这就是原因代码正在处理4.4.5 但表达式++(i|i) 的评估与C 中++ 运算符的定义相悖 所以稍后在新版本的 GCC 中,++(i|i) 会产生左值错误。仅限。
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 2021-04-09
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    相关资源
    最近更新 更多