Das hatte ich in meinem folgenden Code erwartet:
#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;
}
Ausdrücke j = ++(i | i);
und j = ++(i & i);
erzeugt Lvalue-Fehler wie folgt:
x.c: In function ‘main’:
x.c:6: error: lvalue required as increment operand
x.c:9: error: lvalue required as increment operand
Aber ich war überrascht, dass der obige Code erfolgreich kompiliert wurde, wie unten:
~$ gcc x.c -Wall
~$ ./a.out
11 11
12 12
Prüfen der obige Code funktioniert korrekt.
Während andere Operatoren Fehler erzeugen (wie ich es verstehe). Auch der bitweise Operator XOR verursacht einen Fehler j = ++(i ^ i);
(überprüfen Sie andere Operatoren erzeugen einen lvalue-Fehler zur Kompilierungszeit).
Was ist der Grund? Ist dies unspezifiziert oder undefiniert? oder bitweise OR AND-Operatoren sind unterschiedlich?
Compiler-Version:
gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)
Aber ich glaube, dass die Compiler-Version kein Grund für uneinheitliches Verhalten sein sollte. Wenn ^
damals nicht kompiliert |
und &
und auch nicht. ansonsten sollte es bei allen funktionieren
Es ist kein Fehler mit diesem Compiler im c99-Modus: gcc x.c -Wall -std=c99
.
Sie haben Recht, dass es nicht kompilieren sollte, und auf den meisten Compilern wird es nicht kompiliert.
(Bitte geben Sie genau an, welcher Compiler/Version KEINEN Compiler-Fehler ausgibt)
Ich kann nur vermuten, dass der Compiler die Identitäten kennt (i | i) == i
und (i & i) == i
und verwendet diese Identitäten, um den Ausdruck wegzuoptimieren, wobei die Variable einfach zurückgelassen wird i
.
Das ist nur eine Vermutung, aber es ergibt für mich sehr viel Sinn.
C11 (n1570), § 6.5.3.1 Präfix-Inkrement- und -Dekrement-Operatoren
Der Operand des Präfix-Inkrement- oder -Dekrement-Operators muss einen atomaren, qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und muss a sein änderbarer lvalue.
C11 (n1570), § 6.3.2.1 Lvalues, Arrays und Funktionsbezeichner
Ein änderbarer Lvalue ist ein Lvalue, der keinen Array-Typ, keinen unvollständigen Typ, keinen konstant qualifizierten Typ hat und, wenn es sich um eine Struktur oder Union handelt, kein Mitglied hat (einschließlich rekursiv jedes Mitglied oder Element aller enthaltenen Aggregate oder Vereinigungen) mit einem const-qualifizierten Typ.
C11 (n1570), § 6.3.2.1 Lvalues, Arrays und Funktionsbezeichner
Ein Lvalue ist ein Ausdruck (mit einem anderen Objekttyp als void
), die möglicherweise ein Objekt bezeichnet.
C11 (n1570), § 3. Begriffe, Definitionen und Symbole
Objekt: Bereich der Datenspeicherung in der Ausführungsumgebung, dessen Inhalt Werte darstellen kann
Soweit ich weiss, möglicherweise bedeutet „seinsfähig, aber noch nicht existent“. Aber (i | i)
nicht in der Lage ist, auf einen Bereich eines Datenspeichers in der Ausführungsumgebung zu verweisen. Daher ist es kein Lvalue. Dies scheint ein Fehler in einer alten gcc-Version zu sein, der seitdem behoben wurde. Aktualisieren Sie Ihren Compiler!
Nur eine Ergänzung zu meiner Frage. Ich habe eine ausführliche Antwort hinzugefügt, damit man sie hilfreich finden kann.
In meinen Codeausdrücken j = ++(i | i);
und j = ++(i & i);
werden nicht für Lvalue-Fehler verursacht?
Wegen der Compiler-Optimierung, wie @abelenky antwortete (i | i) == i
und (i & i) == i
. Das ist genau RICHTIG.
In meinem Compiler (gcc version 4.4.5)
, jeder Ausdruck, der eine einzelne Variable enthält, und das Ergebnis bleibt unverändert; optimiert in eine einzelne Variable (etwas namens kein Ausdruck).
zum Beispiel:
j = i | i ==> j = i
j = i & i ==> j = i
j = i * 1 ==> j = i
j = i - i + i ==> j = i
==>
meint optimized to
Um es zu beobachten habe ich einen kleinen C-Code geschrieben und diesen damit zerlegt gcc -S
.
C-Code: (Kommentare lesen)
#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);
}
Assembly-Ausgabe: (Kommentare lesen)
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
Im obigen Assemblercode werden alle Ausdrücke in folgenden Code umgewandelt:
movl 28(%esp), %eax
movl %eax, 24(%esp)
das ist gleichbedeutend mit j = i
im C-Code. Daher j = ++(i | i);
und und j = ++(i & i);
optimiert sind j = ++i
.
Notiz: j = (i | i)
ist eine Aussage wo als Ausdruck (i | i)
keine Anweisung (nop) in C
Daher konnte mein Code erfolgreich kompiliert werden.
Warum j = ++(i ^ i);
oder j = ++(i * i);
, j = ++(i | k);
Lvalue-Fehler auf meinem Compiler erzeugen?
Weil jeder Ausdruck den konstanten Wert oder den nicht änderbaren lvalue hat (nicht optimierter Ausdruck).
wir können mit beobachten asm
Code
#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;
}
Montagecode: (Kommentare lesen)
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
Daher so produziert dies ein lvalue error
weil operand kein änderbarer lvalue ist. Und uneinheitliches Verhalten ist auf Compiler-Optimierung in gcc-4.4 zurückzuführen.
Warum erzeugen neue gcc-Compiler (oder die meisten Compiler) einen lvalue-Fehler?
Weil Auswertung des Ausdrucks ++(i | i)
und ++(i & i)
verbietet die eigentliche Definition des Increment(++)-Operators.
Laut Dennis M. Ritchies Buch “Die Programmiersprache C” im Abschnitt “2.8 Inkrement- und Dekrementoperatoren” Seite 44.
Die Inkrement- und Dekrementoperatoren können nur auf Variablen angewendet werden; ein Ausdruck wie (i+j)++ ist illegal. Der Operand muss ein änderbarer lvalue vom arithmetischen oder Zeigertyp sein.
Ich habe neu getestet gcc-Compiler 4.47 hier es erzeugt einen Fehler, wie ich erwartet hatte. ich auch getestet auf tcc-Compiler.
Feedback/Kommentare dazu wären toll.
Ich denke überhaupt nicht, dass es sich um einen Optimierungsfehler handelt, denn wenn doch, dann sollte es überhaupt keinen Fehler geben. Wenn ++(i | i)
ist darauf optimiert ++(i)
dann sollte es keinen Fehler geben, weil (i)
ist ein lvalue.
IMHO, ich denke, dass der Compiler sieht (i | i)
als Ausdrucksausgabe, die natürlich rvalue ausgibt, aber den Inkrementoperator ++
erwartet, dass ein Lvalue es ändert, daher der Fehler.
Es funktioniert mit GCC 4.4 für mich … Es scheint ein korrigierter Fehler zu sein.
– md5
13. Februar 2013 um 18:21 Uhr
@texasbruce es sollte nicht kompiliert werden
(i|i)
ist Ausdruck keine Variable siehe++(i|i)
etwas wie(i|i) =
(i|i)` + 1`, das ist ein Lvalue-Fehler.– Grijesh Chauhan
13. Februar 2013 um 18:21 Uhr
Was ist der Spezifisch Compiler-Version, die Sie verwenden? Ich habe zwei verschiedene gcc-Versionen ausprobiert, Clang und Intel. Alle geben eine Compiler-Diagnose aus.
– Hochofen
13. Februar 2013 um 18:22 Uhr
Das von Ihnen beobachtete Verhalten tritt nicht auf neuer gcc-Releases oder andere Compiler. Warum fällt es Ihnen schwer, das zu verstehen?
– Hochofen
13. Februar 2013 um 18:29 Uhr
@Blastfurnace Es ist denkbar dass die spezifische Version von gcc des OP korrekt ist und alle anderen falsch liegen. Es ist in diesem Fall außerordentlich unwahrscheinlich, aber es ist immer noch wahr, dass “sollte this compile”-Fragen können nur anhand des Sprachstandards beantwortet werden, nicht anhand des Verhaltens von Compilern. (Heute bin ich die Art von Pedanten, die auf einem Unterschied zwischen “das soll ein ungültiges Programm sein” und “das ist kein Technisch gesehen kein ungültiges Programm, aber viel Glück beim Finden eines Compilers, der es unterstützt.)
– zol
13. Februar 2013 um 18:33 Uhr