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

111

Nach der Recherche habe ich gelesen, dass der Inkrement-Operator für den Operanden ein modifizierbares Datenobjekt benötigt: https://en.wikipedia.org/wiki/Increment_and_decrement_operators .

Daraus ergibt sich, dass es einen Kompilierungsfehler gibt, da (a+b)es sich um eine temporäre Ganzzahl handelt und daher nicht geändert werden kann.

Ist dieses Verständnis richtig? Ich habe zum ersten Mal versucht, ein Problem zu untersuchen. Wenn ich also nach etwas hätte suchen sollen, geben Sie mir bitte Bescheid.

dng
quelle
35
Das ist nicht schlecht in Bezug auf die Forschung. Du bist auf dem richtigen Weg.
Geschichtenerzähler - Unslander Monica
35
Was erwarten Sie von dem Ausdruck?
Qrdl
4
gemäß C11-Standard 6.5.3.1: Der Operand des Präfix-Inkrement- oder Dekrement-Operators muss einen atomaren, qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und ein modifizierbarer Wert sein
Christian Gibbons
10
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 angemessene Überlegung abgelehnt." - Stan Kelly-Bootle
Andrew Morton
5
Ich denke, eine Folgefrage ist, warum Sie dies jemals tun möchten, wenn c = a + b + 1Ihre Absicht klarer wird und die Eingabe auch kürzer ist. Die Inkrement- / Dekrement-Operatoren führen zwei Dinge aus: 1. Sie und ihr Argument bilden einen Ausdruck (der z. B. in einer for-Schleife verwendet werden kann). 2. Sie ändern das Argument. In Ihrem Beispiel verwenden Sie Eigenschaft 1., aber nicht Eigenschaft 2., da Sie das geänderte Argument wegwerfen. Wenn Sie Eigenschaft 2 nicht benötigen und nur den Ausdruck möchten, können Sie einfach einen Ausdruck schreiben, z. B. x + 1 anstelle von x ++.
Trevor

Antworten:

117

Es ist nur eine Regel, das ist alles und möglicherweise dazu da, (1) das Schreiben von C-Compilern zu vereinfachen, und (2) niemand hat das C-Standardkomitee davon überzeugt, es zu lockern.

Informell gesehen können Sie nur schreiben, ++foowenn fooauf der linken Seite eines Zuweisungsausdrucks wie angezeigt werden kann foo = bar. Da Sie nicht schreiben können a + b = bar, können Sie auch nicht schreiben ++(a + b).

Es gibt keinen wirklichen Grund, warum a + bes keine temporäre Funktion geben könnte, mit der gearbeitet werden ++kann, und das Ergebnis davon ist der Wert des Ausdrucks ++(a + b).

Bathseba
quelle
4
Ich denke, Punkt (1) trifft den Nagel auf den Kopf. Nur die Regeln für die vorübergehende Materialisierung in C ++ zu betrachten, kann den Magen verdrehen (aber es ist mächtig, muss man sagen).
Geschichtenerzähler - Unslander Monica
4
@StoryTeller: Im Gegensatz zu unserer geliebten Sprache C ++ wird C immer noch relativ trivial in Assembly kompiliert.
Bathseba
29
Hier ist meiner Meinung nach ein echter Grund: Es wäre eine schreckliche Verwirrung, wenn ++manchmal ein Nebeneffekt darin besteht, etwas zu modifizieren, und manchmal einfach nicht.
Aschepler
5
@dng: In der Tat ist es; Aus diesem Grund wurden die Begriffe lvalue und rvalue eingeführt, obwohl die Dinge heutzutage komplizierter sind (insbesondere in C ++). Zum Beispiel kann eine Konstante niemals ein Wert sein: So etwas wie 5 = a macht keinen Sinn.
Bathseba
6
@Bathsheba Das erklärt, warum 5 ++ auch einen Kompilierungsfehler verursacht
dng
40

Der C11-Standard besagt in Abschnitt 6.5.3.1

Der Operand des Präfix-Inkrement- oder Dekrement-Operators muss einen atomaren, qualifizierten oder nicht qualifizierten Real- oder Zeigertyp haben und ein modifizierbarer Wert sein

Und "modifizierbarer Wert" ist in Abschnitt 6.3.2.1 Unterabschnitt 1 beschrieben

Ein l-Wert ist ein Ausdruck (mit einem anderen Objekttyp als void), der möglicherweise ein Objekt bezeichnet. Wenn ein Wert bei der Auswertung kein Objekt angibt, ist das Verhalten undefiniert. Wenn ein Objekt einen bestimmten Typ haben soll, wird der Typ durch den Wert angegeben, der zur Bezeichnung des Objekts verwendet wird. Ein modifizierbarer l-Wert ist ein l-Wert, der keinen Array-Typ hat, keinen unvollständigen Typ hat, keinen const-qualifizierten Typ hat und, wenn es sich um eine Struktur oder Vereinigung handelt, kein Mitglied hat (einschließlich rekursiv eines Mitglieds) oder Element aller enthaltenen Aggregate oder Gewerkschaften) mit einem const-qualifizierten Typ.

Es (a+b)handelt sich also nicht um einen veränderbaren l-Wert und ist daher nicht für den Präfix-Inkrement-Operator geeignet.

Christian Gibbons
quelle
1
Ihre Schlussfolgerung aus diesen Definitionen fehlt ... Sie möchten sagen, dass (a + b) möglicherweise kein Objekt bezeichnet, aber diese Absätze erlauben dies nicht.
hkBst
21

Du hast Recht. Der ++versucht, den neuen Wert der ursprünglichen Variablen zuzuweisen. So ++animmt den Wert a, fügt 1ihn und dann zuweisen zurück a. Da (a + b), wie Sie sagten, ein temporärer Wert und keine Variable mit zugewiesener Speicheradresse ist, kann die Zuweisung nicht durchgeführt werden.

Roee Gavirel
quelle
12

Ich denke, Sie haben meistens Ihre eigene Frage beantwortet. Ich könnte eine kleine Änderung an Ihrer Formulierung vornehmen und "temporäre Variable" durch "rvalue" ersetzen, wie von C. Gibbons erwähnt.

Die Begriffe Variable, Argument, temporäre Variable usw. werden klarer, wenn Sie das Speichermodell von C kennenlernen (dies scheint eine schöne Übersicht zu sein: https://www.geeksforgeeks.org/memory-layout-of-c-program/ ).

Der Begriff "rvalue" mag undurchsichtig erscheinen, wenn Sie gerade erst anfangen. Ich hoffe, dass das Folgende bei der Entwicklung einer Intuition darüber hilft.

Lvalue / rvalue sprechen über die verschiedenen Seiten eines Gleichheitszeichens (Zuweisungsoperator): lvalue = linke Seite (Kleinbuchstabe L, keine "Eins") rvalue = rechte Seite

Wenn Sie ein wenig darüber lernen, wie C Speicher (und Register) verwendet, können Sie erkennen, warum die Unterscheidung wichtig ist. In breiten Pinselstrichen erzeugt der Compiler eine Liste der Anweisungen in Maschinensprache, die das Ergebnis eines Ausdrucks (der R - Wert) berechnet und dann legt dieses Ergebnis irgendwo (der L - Wert). Stellen Sie sich einen Compiler vor, der sich mit dem folgenden Codefragment befasst:

x = y * 3

Im Assembly-Pseudocode sieht es möglicherweise so aus wie in diesem 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 Wert funktionieren kann.

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

Nebenbei bemerkt: Die ++ - Anweisung gibt es seit 1969 (obwohl sie in Cs Vorgänger B begann):

(Ken Thompsons) Beobachtung (war), dass die Übersetzung 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 hier der Einfachheit halber verlinkt ist: http://www.bell-labs.com/ usr / dmr / www / chist.html wo Sie nach "++" suchen können.

jgreve
quelle
6

Der Grund ist, dass der Standard erfordert, dass der Operand ein l-Wert ist. Der Ausdruck (a+b)ist kein Wert, daher ist das Anwenden des Inkrementoperators nicht zulässig.

Nun könnte man sagen "OK, das ist in der Tat der Grund, aber es gibt tatsächlich keinen anderen * wirklichen * Grund als diesen" , aber unglücklicherweise erfordert der spezielle Wortlaut, wie der Bediener tatsächlich arbeitet , dass dies der Fall ist.

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

Natürlich können Sie nicht schreiben, E += 1wenn dies Ekein Wert ist. Was schade ist, denn man hätte genauso gut sagen können: "Inkrementiere E um eins" und fertig sein. In diesem Fall wäre es (im Prinzip) durchaus möglich, den Operator auf einen Nicht-Wert anzuwenden, was den Compiler etwas komplexer macht.

Jetzt könnte die Definition trivial umformuliert werden (ich denke, 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 ist, die möglichen Auswirkungen jedoch enorm sind, ist dies nie geschehen und wird wahrscheinlich auch nie eintreten.

Wenn Sie zusätzlich zu C C ++ in Betracht ziehen (Frage ist mit C gekennzeichnet, es gab jedoch 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 ++ könnte das Ergebnis von (a+b)etwas sein, das Sie überhaupt nicht inkrementieren können, oder das Inkrementieren könnte sehr erhebliche Nebenwirkungen haben (nicht nur das Hinzufügen von 1). Der Compiler muss in der Lage sein, damit umzugehen und problematische Fälle zu diagnostizieren, sobald sie auftreten. Bei einem Wert ist das immer noch trivial zu überprüfen. Nicht so für irgendeinen zufälligen Ausdruck in einer Klammer, den Sie auf das arme Ding werfen.
Dies ist kein wirklicher Grund, warum es nicht konnte getan werden, aber es ist sicher eine Erklärung dafür, warum die Leute, die dies implementiert haben, nicht gerade begeistert sind, eine solche Funktion hinzuzufügen, die nur sehr wenigen Leuten sehr wenig Nutzen verspricht.

Damon
quelle
3

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

Casper B. Hansen
quelle
3

++ versucht, der ursprünglichen Variablen den Wert zuzuweisen, und da (a + b) ein temporärer Wert ist, kann die Operation nicht ausgeführt werden. Und sie sind im Grunde genommen Regeln der C-Programmierkonventionen, um die Programmierung zu vereinfachen. Das ist es.

Babu Chandermani
quelle
2

Wenn ++ (a + b) Ausdruck ausgeführt wird, dann zum Beispiel:

int a, b;
a = 10;
b = 20;
/* NOTE :
 //step 1: expression need to solve first to perform ++ operation over operand
   ++ ( exp );
// in your case 
   ++ ( 10 + 20 );
// step 2: result of that inc by one 
   ++ ( 30 );
// here, you're applying ++ operator over constant value and it's invalid use of ++ operator 
*/
++(a+b);
Jeet Parikh
quelle