我期待以下代码:
#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);
(在编译时检查其他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
。
答案 0 :(得分:28)
你是对的,它不应该编译,并且在大多数编译器上,它不会编译
(请准确说明哪个编译器/版本没有给你编译错误)
我只能假设编译器知道(i | i) == i
和(i & i) == i
的身份,并使用这些身份来优化表达式,只留下变量i
。
这只是猜测,但对我来说很有意义。
答案 1 :(得分:25)
这是最近GCC版本中已经解决的错误。
这可能是因为编译器优化i & i
到i
和i | i
到i
。这也解释了为什么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,但增量运算符++
需要左值来改变它,从而产生错误。