Ausdrücke “j = ++(i | i); und j = ++(i & i); sollten ein lvalue-Fehler sein?

Lesezeit: 10 Minuten

Benutzeravatar von Grijesh Chauhan
Grijesh Chauhan

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.

  • 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

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.

  • Aus dem OP: gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5).

    – Richard J.Ross III

    13. Februar 2013 um 18:26 Uhr


  • Beachten Sie, dass i^i vereinfacht zu 0also gibt es nichts zu erhöhen

    – John Dvorak

    13. Februar 2013 um 18:27 Uhr

Dies ist ein Fehler, der in neueren GCC-Versionen behoben wurde.

Es liegt wahrscheinlich daran, dass der Compiler optimiert i & i zu i und i | i zu i. Das erklärt auch, warum der xor-Operator nicht funktioniert hat; i ^ i optimiert werden würde 0was kein änderbarer lvalue ist.

  • @Antonijn danke Antonijn gute Antwort. Und ziemlich ähnlich zu abelenky’s awnser. Ich habe einen älteren akzeptiert. Vielen Dank!

    – Grijesh Chauhan

    14. Februar 2013 um 17:27 Uhr

  • Kennen Sie einen Fehlerbericht oder Commit, auf den Sie verlinken könnten?

    – Richard Hansen

    20. Juli 2013 um 18:48 Uhr

  • @RichardHansen Nein, aber aus Tests, die ich selbst durchgeführt habe, konnte ich schließen, dass in der GCC-Version, die ich damals verwendet habe (möglicherweise 4.6, nicht sicher), der Fehler behoben wurde.

    – Antonijn

    20. Juli 2013 um 19:05 Uhr

Benutzeravatar von md5
md5

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!

  • Danke, dass Sie Standardesisch ausgegraben haben, aber könnten Sie bitte auch die Definition von “potenziell ein Objekt bezeichnen” und die Einschränkungen dafür zitieren ++ Operator (wo steht, dass der Operand ein Lvalue sein muss)? Nur um alle Zweifel auszuräumen.

    – zol

    13. Februar 2013 um 18:34 Uhr


  • Irgendwo in der Nähe von 6.3.2.1 sollte es eine Sprache geben, die genau erklärt, welche Ausdrücke “potentiell ein Objekt bezeichnen” können, das ist es, was ich hier wirklich sehen möchte.

    – zol

    13. Februar 2013 um 18:44 Uhr

  • @Zack Ich würde denken, dass Literale von der Definition des Objekts ausgeschlossen werden sollen. Zum Beispiel das Wörtliche "hello" ist ein lvalue und von Typ Objekt, aber kein Objekt. Daher die warnenden Worte “potentiell ein Objekt”. Aber ich weiß nicht, ob es im Standard eine explizite Definition dafür gibt.

    – PP

    13. Februar 2013 um 18:47 Uhr


  • @Kirilenko Hallo Kirilenko! Ihre Antwort war sehr hilfreich für mich 🙂 danke

    – Grijesh Chauhan

    14. Februar 2013 um 17:28 Uhr

Benutzeravatar von Grijesh Chauhan
Grijesh Chauhan

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.

  • Ghasan ++(i) ist kein Wert. Vielen Dank 🙂

    – Grijesh Chauhan

    16. März 2013 um 17:33 Uhr


  • @ GrijeshChauhan habe ich nicht gesagt ++(i) ist ein lvalue, aber (i). Deshalb wird behauptet, dass der Compiler den Ausdruck optimiert (i | i) zu (i) stimmt nicht, weil ++(i) ist gültig.

    – Ghasan غسان

    16. März 2013 um 17:58 Uhr

  • Ghasan Entschuldigung, ich habe Sie falsch verstanden. Ok lesen Sie meine Antwort. Was ich das gezeigt habe … in GCC 4.4.5 ++(i|i) ist darauf optimiert ++(i) Das ist der Grund, warum Code arbeitet 4.4.5 Aber Bewertung des Ausdrucks ++(i|i) ist gegen die Definition von ++ Operator in C Also später in der neuen Version von GCC die ++(i|i) einen Lvalue-Fehler erzeugen. Nur.

    – Grijesh Chauhan

    16. März 2013 um 18:03 Uhr

  • Ghasan ++(i) ist kein Wert. Vielen Dank 🙂

    – Grijesh Chauhan

    16. März 2013 um 17:33 Uhr


  • @ GrijeshChauhan habe ich nicht gesagt ++(i) ist ein lvalue, aber (i). Deshalb wird behauptet, dass der Compiler den Ausdruck optimiert (i | i) zu (i) stimmt nicht, weil ++(i) ist gültig.

    – Ghasan غسان

    16. März 2013 um 17:58 Uhr

  • Ghasan Entschuldigung, ich habe Sie falsch verstanden. Ok lesen Sie meine Antwort. Was ich das gezeigt habe … in GCC 4.4.5 ++(i|i) ist darauf optimiert ++(i) Das ist der Grund, warum Code arbeitet 4.4.5 Aber Bewertung des Ausdrucks ++(i|i) ist gegen die Definition von ++ Operator in C Also später in der neuen Version von GCC die ++(i|i) einen Lvalue-Fehler erzeugen. Nur.

    – Grijesh Chauhan

    16. März 2013 um 18:03 Uhr

1405440cookie-checkAusdrücke “j = ++(i | i); und j = ++(i & i); sollten ein lvalue-Fehler sein?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy