Warum gibt c = ++(a+b) einen Kompilierungsfehler?

Lesezeit: 8 Minuten

Benutzeravatar von dng
dng

Nach Recherchen habe ich gelesen, dass der Inkrementoperator erfordert, dass der Operand ein änderbares Datenobjekt hat: https://en.wikipedia.org/wiki/Increment_and_decrement_operators.

Daraus schätze ich, dass es da Kompilierungsfehler gibt (a+b) ist eine temporäre Ganzzahl und kann daher nicht geändert werden.

Ist dieses Verständnis richtig? Dies war mein erster Versuch, ein Problem zu recherchieren. Wenn es also etwas gab, wonach ich hätte suchen sollen, geben Sie bitte Bescheid.

  • Das ist nicht schlecht in Bezug auf die Forschung. Du bist auf dem richtigen Weg.

    – StoryTeller – Unslander Monica

    20. Juni 2018 um 15:01 Uhr

  • Was erwarten Sie von dem Ausdruck?

    – qrdl

    20. Juni 2018 um 15:02 Uhr

  • nach C11-Standard 6.5.3.1: Der Operand des Präfixinkrement- oder -dekrementoperators muss einen atomaren, qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und ein modifizierbarer L-Wert sein

    – Christian Gibbons

    20. Juni 2018 um 15:07 Uhr

  • Wie soll die 1 zwischen a und b verteilt werden? „Sollten Array-Indizes bei 0 oder 1 beginnen? Mein Kompromiss von 0,5 wurde ohne, dachte ich, gebührende Überlegung abgelehnt.“ — Stan Kelly-Bootle

    – Andreas Morton

    20. Juni 2018 um 18:53 Uhr

  • Ich denke, eine Folgefrage ist, warum Sie dies wann jemals tun möchten c = a + b + 1 macht Ihre Absicht klarer und ist auch kürzer zu tippen. Die Inkrement/Dekrement-Operatoren tun zwei Dinge: 1. sie bilden mit ihrem Argument einen Ausdruck (der zB in einer for-Schleife verwendet werden kann), 2. sie modifizieren das Argument. In Ihrem Beispiel verwenden Sie Eigenschaft 1. aber nicht Eigenschaft 2., da Sie das geänderte Argument wegwerfen. Wenn Sie die Eigenschaft 2. nicht brauchen und nur den Ausdruck wollen, dann können Sie einfach einen Ausdruck schreiben, zB x+1 statt x++.

    – Trevor

    21. Juni 2018 um 1:10 Uhr

Benutzeravatar von Bathsheba
Bathseba

Es ist nur eine Regel, das ist alles, und ist möglicherweise dazu da, (1) es einfacher zu machen, C-Compiler zu schreiben, und (2) niemand hat das C-Standards-Komitee davon überzeugt, es zu lockern.

Informell kann man nur schreiben ++foo wenn foo kann auf der linken Seite eines Zuweisungsausdrucks wie z foo = bar. Da du nicht schreiben kannst a + b = bardu kannst nicht schreiben ++(a + b) entweder.

Es gibt keinen wirklichen Grund dafür a + b konnte kein temporäres Ergebnis liefern ++ funktionieren kann, und das Ergebnis davon ist der Wert des Ausdrucks ++(a + b).

  • Ich denke Punkt (1) trifft den Nagel auf den Kopf. Allein der Blick auf die Regeln für die temporäre Materialisierung in C++ kann einem den Magen umdrehen (aber es ist mächtig, muss ich sagen).

    – StoryTeller – Unslander Monica

    20. Juni 2018 um 15:19 Uhr


  • @StoryTeller: Im Gegensatz zu unserer geliebten Sprache C++ lässt sich C immer noch relativ trivial in Assembler kompilieren.

    – Bathseba

    20. Juni 2018 um 15:20 Uhr

  • Hier ist meiner Meinung nach ein echter Grund: Es wäre eine schreckliche Verwirrung, wenn ++ hatte manchmal den Nebeneffekt, etwas zu modifizieren und manchmal einfach nicht.

    – Aschepler

    20. Juni 2018 um 20:14 Uhr

  • @dng: In der Tat ist es; deshalb wurden die Begriffe lvalue und rvalue eingeführt, obwohl die Dinge heutzutage (insbesondere in C++) komplizierter sind. Beispielsweise kann eine Konstante niemals ein L-Wert sein: etwas wie 5 = a macht keinen Sinn.

    – Bathseba

    21. Juni 2018 um 7:34 Uhr


  • @Bathsheba Das erklärt, warum 5++ auch einen Kompilierungsfehler verursacht

    – dng

    21. Juni 2018 um 8:43 Uhr

Benutzeravatar von Christian Gibbons
Christian Gibbons

Der C11-Standard gibt in Abschnitt 6.5.3.1 an

Der Operand des Präfixinkrement- oder -dekrementoperators muss einen atomaren, qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und ein modifizierbarer L-Wert sein

Und “modifizierbarer lvalue” wird in Abschnitt 6.3.2.1 Unterabschnitt 1 beschrieben

Ein lvalue ist ein Ausdruck (mit einem anderen Objekttyp als void), der potenziell ein Objekt bezeichnet; Wenn ein lvalue bei der Auswertung kein Objekt bezeichnet, ist das Verhalten undefiniert. Wenn von einem Objekt gesagt wird, dass es einen bestimmten Typ hat, wird der Typ durch den lvalue angegeben, der zur Bezeichnung des Objekts verwendet wird. Ein änderbarer Lvalue ist ein Lvalue, der keinen Array-Typ hat, keinen unvollständigen Typ hat, keinen durch Konstanten 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 Unions) mit einem const-qualifizierten Typ.

So (a+b) ist kein änderbarer lvalue und kommt daher nicht für den Präfixinkrementoperator in Frage.

  • Ihre Schlussfolgerung aus diesen Definitionen fehlt … Sie möchten sagen, dass (a + b) möglicherweise kein Objekt bezeichnet, aber diese Absätze lassen dies nicht zu.

    – hkBst

    21. Juni 2018 um 11:37 Uhr


Du hast Recht. das ++ versucht, der ursprünglichen Variable den neuen Wert zuzuweisen. So ++a nimmt den Wert von an afügt hinzu 1 zu und weisen Sie es dann wieder zu a. Da, wie Sie sagten, (a+b) ein temporärer Wert ist und keine Variable mit zugewiesener Speicheradresse, kann die Zuweisung nicht durchgeführt werden.

Benutzeravatar von jgreve
jgreve

Ich denke, Sie haben Ihre Frage größtenteils selbst beantwortet. Ich könnte eine kleine Änderung an Ihrer Formulierung vornehmen und “temporäre Variable” durch “rvalue” ersetzen, wie C.Gibbons erwähnte.

Die Begriffe Variable, Argument, temporäre Variable usw. werden klarer, wenn Sie mehr über das Speichermodell von C erfahren (dies sieht nach einem schönen Überblick aus: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Der Begriff „rvalue“ mag undurchsichtig erscheinen, wenn Sie gerade erst anfangen, daher hoffe ich, dass das Folgende dabei hilft, eine Intuition darüber zu entwickeln.

Lvalue/rvalue sprechen von den verschiedenen Seiten eines Gleichheitszeichens (Zuweisungsoperator): lvalue = linke Seite (kleines L, keine „Eins“) rvalue = rechte Seite

Ein wenig darüber zu lernen, wie C Speicher (und Register) verwendet, wird hilfreich sein, um zu sehen, warum die Unterscheidung wichtig ist. Im breite Pinselstricheerstellt der Compiler eine Liste von Anweisungen in Maschinensprache, die das Ergebnis eines Ausdrucks (den rvalue) berechnen, und dann setzt das Ergebnis irgendwo (der lvalue). Stellen Sie sich einen Compiler vor, der mit dem folgenden Codefragment zu tun hat:

x = y * 3

In Assembly Pseudocode it könnte Sieh etwa so aus wie dieses Spielzeugbeispiel:

load register A with the value at memory address y
load register B with a value of 3
multiply register A and B, saving the result in A
write register A to memory address x

Der ++-Operator (und sein –Gegenstück) benötigen ein „irgendwo“ zum Ändern, im Wesentlichen alles, was als lvalue funktionieren kann.

Das Verständnis des C-Speichermodells ist hilfreich, da Sie eine bessere Vorstellung davon bekommen, wie Argumente an Funktionen übergeben werden und (eventuell) wie Sie mit dynamischer Speicherzuweisung wie der Funktion malloc() arbeiten. Aus ähnlichen Gründen könnten Sie irgendwann eine einfache Assembler-Programmierung studieren, um eine bessere Vorstellung davon zu bekommen, was der Compiler tut. Auch wenn Sie verwenden gccdas -S Option “Nach der eigentlichen Kompilierungsphase anhalten; nicht zusammenbauen.” kann interessant sein (obwohl ich empfehlen würde, es an einem zu versuchen klein Codefragment).

Nur so nebenbei: Die ++ Anweisung gibt es seit 1969 (obwohl es in Cs Vorgänger B begann):

(Ken Thompsons) Beobachtung (war), dass die Translation von ++x kleiner war als die von x=x+1.“

Wenn Sie dieser Wikipedia-Referenz folgen, gelangen Sie zu einem interessanten Artikel von Dennis Ritchie (das “R” in “K&R C”) über die Geschichte der C-Sprache, der der Einfachheit halber hier verlinkt ist: http://www.bell-labs.com/usr/dmr/www/chist.html wo Sie nach “++” suchen können.

Damons Benutzeravatar
Damon

Der Grund dafür ist, dass der Standard verlangt, dass der Operand ein L-Wert ist. Der Ausdruck (a+b) ist kein Lvalue, daher ist die Anwendung des Inkrementoperators nicht zulässig.

Nun könnte man sagen “OK, das ist in der Tat der Grund, aber es gibt eigentlich keinen anderen *echten* Grund als diesen”aber leider der besondere Wortlaut, wie der Operator sachlich funktioniert tut verlangen, dass dies der Fall ist.

Der Ausdruck ++E entspricht (E+=1).

Natürlich kann man nicht schreiben E += 1 wenn E ist kein lvalue. Schade, denn man hätte genauso gut sagen können: “erhöht E um eins” und fertig sein. In diesem Fall wäre die Anwendung des Operators auf einen Nicht-lvalue (im Prinzip) durchaus möglich, auf Kosten einer etwas komplexeren Kompilierung.

Jetzt könnte die Definition trivial umformuliert werden (ich glaube, es ist nicht einmal ursprünglich C, sondern ein Erbstück von B), aber dies würde die Sprache grundlegend in etwas ändern, das nicht mehr mit den früheren Versionen kompatibel ist. Da der mögliche Nutzen eher gering, aber die möglichen Auswirkungen riesig sind, ist das nie passiert und wird wahrscheinlich nie passieren.

Wenn Sie C++ zusätzlich zu C betrachten (Frage ist mit C gekennzeichnet, aber es gab Diskussionen über Operatorüberladungen), wird die Geschichte noch komplizierter. In C ist es schwer vorstellbar, dass dies der Fall sein könnte, aber in C++ ist das Ergebnis von (a+b) könnte sehr gut etwas sein, das Sie überhaupt nicht erhöhen können, oder das Erhöhen könnte sehr beträchtliche Nebenwirkungen haben (nicht nur das Hinzufügen von 1). Der Compiler muss damit umgehen können und problematische Fälle diagnostizieren, sobald sie auftreten. Bei einem lvalue ist das immer noch ziemlich trivial zu überprüfen. Nicht so für irgendeinen willkürlichen Ausdruck in einer Klammer, den Sie auf das arme Ding werfen.
Das ist kein real Grund warum es konnte nicht getan werden, aber es liefert sicherlich eine Erklärung dafür, warum die Leute, die dies implementiert haben, nicht gerade begeistert sind, ein solches Feature hinzuzufügen, das sehr wenig Nutzen für sehr wenige Leute verspricht.

Benutzeravatar von Casper B. Hansen
Casper B. Hansen

(a+b) ergibt einen rvalue, der nicht erhöht werden kann.

Benutzeravatar von Babu Chandermani
Babu Chandermani

++ versucht, den Wert an die ursprüngliche Variable zu übergeben, und da (a+b) ein temporärer Wert ist, kann es die Operation nicht ausführen. Und sie sind im Grunde Regeln der C-Programmierkonventionen, um das Programmieren einfach zu machen. Das ist es.

1422110cookie-checkWarum gibt c = ++(a+b) einen Kompilierungsfehler?

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

Privacy policy