Ist das Kurzschließen logischer Operatoren vorgeschrieben? Und Bewertungsauftrag?

140

Erfordert der ANSI-Standard , dass die logischen Operatoren in C oder C ++ kurzgeschlossen werden?

Ich bin verwirrt, weil ich mich an das K & R-Buch erinnere, in dem es heißt, Ihr Code sollte nicht davon abhängen, dass diese Vorgänge kurzgeschlossen werden, da dies möglicherweise nicht der Fall ist. Könnte jemand bitte darauf hinweisen, wo im Standard gesagt wird, dass logische Operationen immer kurzgeschlossen sind? Ich interessiere mich hauptsächlich für C ++, eine Antwort auch für C wäre toll.

Ich erinnere mich auch daran, dass ich gelesen habe (ich kann mich nicht erinnern, wo), dass die Auswertungsreihenfolge nicht streng definiert ist, sodass Ihr Code nicht davon abhängen oder annehmen sollte, dass Funktionen innerhalb eines Ausdrucks in einer bestimmten Reihenfolge ausgeführt werden: Am Ende einer Anweisung werden alle Funktionen, auf die verwiesen wird wird aufgerufen worden sein, aber der Compiler hat die Freiheit, die effizienteste Reihenfolge auszuwählen.

Gibt der Standard die Bewertungsreihenfolge dieses Ausdrucks an?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";
Joe Pineda
quelle
12
Vorsicht: Dies gilt für POD-Typen. Aber wenn Sie den Operator && oder Operator || überladen Für eine bestimmte Klasse sind dies NICHT Ich wiederhole NICHT Verknüpfung. Aus diesem Grund wird empfohlen, diese Operatoren NICHT für Ihre eigenen Klassen zu definieren.
Martin York
Ich habe diese Operatoren vor einiger Zeit neu definiert, als ich eine Klasse erstellt habe, die einige grundlegende boolesche Algebraoperationen ausführt. Wahrscheinlich sollte ein Warnkommentar "dies zerstört Kurzschluss und Links-Rechts-Auswertung!" falls ich das vergesse. Auch überladen * / + und machte sie zu ihren Synonymen :-)
Joe Pineda
Funktionsaufrufe in einem if-Block zu haben, ist keine gute Programmierpraxis. Lassen Sie immer eine Variable deklarieren, die den Rückgabewert der Methode enthält, und verwenden Sie sie im if-Block.
SR Chaitanya
6
@SRChaitanya Das ist nicht richtig. Was Sie willkürlich als schlechte Praxis bezeichnen, wird ständig ausgeführt, insbesondere mit Funktionen, die wie hier Boolesche Werte zurückgeben.
Marquis von Lorne

Antworten:

154

Ja, für Bediener ||und &&sowohl in C- als auch in C ++ - Standards sind Kurzschluss- und Auswertungsreihenfolge erforderlich .

Der C ++ - Standard sagt (es sollte eine äquivalente Klausel im C-Standard geben):

1.9.18

Bei der Bewertung der folgenden Ausdrücke

a && b
a || b
a ? b : c
a , b

Unter Verwendung der eingebauten Bedeutung der Operatoren in diesen Ausdrücken gibt es nach der Auswertung des ersten Ausdrucks einen Sequenzpunkt (12).

In C ++ gibt es eine zusätzliche Falle: Kurzschluss gilt NICHT für Typen, die Operatoren ||und überladen &&.

Fußnote 12: Die in diesem Absatz angegebenen Operatoren sind die in Abschnitt 5 beschriebenen integrierten Operatoren. Wenn einer dieser Operatoren in einem gültigen Kontext überladen ist (Abschnitt 13) und somit eine benutzerdefinierte Operatorfunktion bezeichnet, bezeichnet der Ausdruck ein Funktionsaufruf, und die Operanden bilden eine Argumentliste ohne einen impliziten Sequenzpunkt zwischen ihnen.

Es wird normalerweise nicht empfohlen, diese Operatoren in C ++ zu überladen, es sei denn, Sie haben eine ganz bestimmte Anforderung. Sie können dies tun, es kann jedoch das erwartete Verhalten im Code anderer Personen beeinträchtigen, insbesondere wenn diese Operatoren indirekt über das Instanziieren von Vorlagen verwendet werden, wobei der Typ diese Operatoren überlastet.

Alex B.
quelle
3
Wusste nicht, dass Kurzschlüsse nicht für überlastete Logikoperationen gelten würden, das ist interessant. Können Sie bitte einen Verweis auf den Standard oder eine Quelle hinzufügen? Ich misstraue dir nicht, ich möchte nur mehr darüber erfahren.
Joe Pineda
4
Ja, das ist logisch. Es dient als Argument für den Operator && (a, b). Es ist die Implementierung, die sagt, was passiert.
Johannes Schaub - litb
10
litb: Es ist einfach nicht möglich, b an den Operator && (a, b) zu übergeben, ohne es auszuwerten. Und es gibt keine Möglichkeit, die Auswertung von b rückgängig zu machen, da der Compiler nicht garantieren kann, dass keine Nebenwirkungen auftreten.
jmucchiello
2
Ich finde das traurig. Ich hätte das gedacht, wenn ich die Operatoren && und || neu definiert hätte und sie sind immer noch völlig deterministisch , der Compiler würde das erkennen und ihre Auswertung kurzschließen: Schließlich ist die Reihenfolge irrelevant und sie garantieren keine Nebenwirkungen!
Joe Pineda
2
@Joe: Der Rückgabewert und die Argumente des Operators können sich jedoch von boolean zu etwas anderem ändern. Ich habe "spezielle" Logik mit DREI Werten implementiert ("wahr", "falsch" und "unbekannt"). Der Rückgabewert ist deterministisch, aber das Kurzschlussverhalten ist nicht angemessen.
Alex B
70

Die Kurzschlussbewertung und die Reihenfolge der Bewertung ist sowohl in C als auch in C ++ ein vorgeschriebener semantischer Standard.

Wenn dies nicht der Fall wäre, wäre Code wie dieser keine gängige Redewendung

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

In Abschnitt 6.5.13 Logischer UND-Operator der C99-Spezifikation (PDF-Link) heißt es

(4). Im Gegensatz zum bitweisen binären & -Operator garantiert der && -Operator eine Auswertung von links nach rechts. Nach der Auswertung des ersten Operanden gibt es einen Sequenzpunkt. Wenn der erste Operand gleich 0 ist, wird der zweite Operand nicht ausgewertet.

In ähnlicher Weise Abschnitt 6.5.14 Logical OR - Operator sagt

(4) Im Gegensatz zum bitweisen | Betreiber, der || Betreiber garantiert Auswertung von links nach rechts; Nach der Auswertung des ersten Operanden gibt es einen Sequenzpunkt. Wenn der erste Operand ungleich 0 ist, wird der zweite Operand nicht ausgewertet.

Ein ähnlicher Wortlaut findet sich in den C ++ - Standards, siehe Abschnitt 5.14 in diesem Entwurf . Wie Prüfer in einer anderen Antwort feststellt, müssen beide Operanden ausgewertet werden, wenn Sie && oder || überschreiben, da dies zu einem regulären Funktionsaufruf wird.

Paul Dixon
quelle
Ah, wonach ich gesucht habe! OK, daher sind sowohl Auswertungsreihenfolge als auch Kurzschluss gemäß ANSI-C 99 vorgeschrieben! Ich würde wirklich gerne die entsprechende Referenz für ANSI-C ++ sehen, obwohl ich zu fast 99% der Meinung bin, dass es dieselbe sein muss.
Joe Pineda
Es ist schwer, einen guten kostenlosen Link für die C ++ - Standards zu finden. Ich habe auf einen Entwurf verwiesen, den ich mit etwas googeln gefunden habe.
Paul Dixon
Richtig für POD-Typen. Aber wenn Sie den Operator && oder den Operator || überladen Dies sind keine Abkürzungen.
Martin York
1
Ja, es ist interessant zu bemerken, dass Sie für Bool immer eine garantierte Bewertungsreihenfolge und ein kurzgeschlossenes Kurzschlussverhalten haben. weil Sie den Operator && für zwei integrierte Typen nicht überladen können. Sie benötigen mindestens einen benutzerdefinierten Typ in den Operanden, damit er sich anders verhält.
Johannes Schaub - litb
Ich wünschte, ich könnte beide Checker und diese Antwort akzeptieren. Da ich hauptsächlich an C ++ interessiert bin, akzeptiere ich das andere, muss aber zugeben, dass dies auch hervorragend ist! Vielen Dank!
Joe Pineda
19

Ja, das ist vorgeschrieben (sowohl Bewertungsreihenfolge als auch Kurzschluss). Wenn in Ihrem Beispiel alle Funktionen true zurückgeben, ist die Reihenfolge der Aufrufe streng von FunktionA, dann von FunktionB und dann von FunktionC. Wird dafür gerne verwendet

if(ptr && ptr->value) { 
    ...
}

Gleiches gilt für den Komma-Operator:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

Man sagt , zwischen den linken und rechten Operanden &&, ||, ,und zwischen den ersten und den zweiten / dritten Operanden ?:(Bedingungsoperator) ist ein „Sequenzpunkt“. Alle Nebenwirkungen werden vor diesem Zeitpunkt vollständig bewertet. Das ist also sicher:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

Beachten Sie, dass der Kommaoperator nicht mit dem syntaktischen Komma verwechselt werden darf, mit dem Dinge getrennt werden:

// order of calls to a and b is unspecified!
function(a(), b());

Der C ++ Standard sagt in 5.14/1:

Die Operatorgruppen && gruppieren sich von links nach rechts. Die Operanden werden beide implizit in den Typ bool konvertiert (Klausel 4). Das Ergebnis ist wahr, wenn beide Operanden wahr und ansonsten falsch sind. Im Gegensatz zu & garantiert && die Auswertung von links nach rechts: Der zweite Operand wird nicht ausgewertet, wenn der erste Operand falsch ist.

Und in 5.15/1:

Die || Bedienergruppen von links nach rechts. Die Operanden werden beide implizit in bool konvertiert (Klausel 4). Es gibt true zurück, wenn einer seiner Operanden true ist, andernfalls false. Im Gegensatz zu |, || garantiert die Bewertung von links nach rechts; Außerdem wird der zweite Operand nicht ausgewertet, wenn der erste Operand als wahr ausgewertet wird.

Es heißt für beide neben denen:

Das Ergebnis ist ein Bool. Alle Nebenwirkungen des ersten Ausdrucks mit Ausnahme der Zerstörung von Provisorien (12.2) treten auf, bevor der zweite Ausdruck bewertet wird.

Darüber hinaus 1.9/18sagt

Bei der Bewertung jedes der Ausdrücke

  • a && b
  • a || b
  • a ? b : C
  • a , b

Unter Verwendung der in diesen Ausdrücken integrierten Bedeutung der Operatoren (5.14, 5.15, 5.16, 5.18) gibt es nach der Auswertung des ersten Ausdrucks einen Sequenzpunkt.

Johannes Schaub - litb
quelle
10

Direkt vom guten alten K & R:

C garantiert dies &&und ||wird von links nach rechts bewertet - wir werden bald Fälle sehen, in denen dies wichtig ist.

John T.
quelle
3
K & R 2. Auflage S. 40. "Durch && oder || verbundene Ausdrücke werden von links nach rechts ausgewertet, und die Auswertung wird beendet, sobald die Wahrheit oder Falschheit des Ergebnisses bekannt ist. Die meisten C-Programme stützen sich auf diese Eigenschaften." Ich kann Ihren zitierten Text nirgendwo im Buch finden. Ist das aus der extrem veralteten 1. Auflage? Bitte klären Sie, wo Sie diesen Text gefunden haben.
Lundin
1
Ok, es stellt sich heraus, dass Sie dieses alte Tutorial zitieren . Es ist von 1974 und höchst irrelevant.
Lundin
6

Sei sehr sehr vorsichtig.

Für grundlegende Typen sind dies Verknüpfungsoperatoren.

Wenn Sie diese Operatoren jedoch für Ihre eigenen Klassen- oder Aufzählungstypen definieren, handelt es sich nicht um Verknüpfungen. Aufgrund dieses semantischen Unterschieds in ihrer Verwendung unter diesen verschiedenen Umständen wird empfohlen, diese Operatoren nicht zu definieren.

Für die operator &&und operator ||für Grundtypen ist die Bewertungsreihenfolge von links nach rechts (andernfalls wäre eine Abkürzung schwierig :-) Für überladene Operatoren, die Sie definieren, handelt es sich im Grunde genommen um syntaktischen Zucker zur Definition einer Methode, und daher ist die Reihenfolge der Bewertung der Parameter nicht definiert.

Martin York
quelle
1
Das Überladen von Operatoren hat nichts damit zu tun, ob der Typ POD ist oder nicht. Um eine Operatorfunktion zu definieren, muss mindestens eines der Argumente eine Klasse (oder Struktur oder Vereinigung) oder eine Aufzählung oder ein Verweis auf eine dieser sein. POD zu sein bedeutet, dass Sie memcpy verwenden können.
Derek Ledbetter
Und das habe ich gesagt. Wenn Sie && für Ihre Klasse überladen, ist es wirklich nur ein Methodenaufruf. Somit können Sie sich nicht auf die Reihenfolge der Auswertung der Parameter verlassen. Natürlich können Sie && für POD-Typen nicht überladen.
Martin York
3
Sie verwenden den Begriff "POD-Typen" falsch. Sie können && für jede Struktur, Klasse, Vereinigung oder Aufzählung, POD oder nicht überladen. Sie können && nicht überladen, wenn beide Seiten numerische Typen oder Zeiger sind.
Derek Ledbetter
Ich habe POD als (char / int / float usw.) nicht als aggressiven POD verwendet (worüber Sie sprechen) und wird normalerweise separat oder expliziter bezeichnet, da es sich nicht um einen eingebauten Typ handelt.
Martin York
2
Sie meinten also "Grundtypen", schrieben aber "POD-Typen"?
Öö Tiib
0

Ihre Frage hängt von der Priorität und Assoziativität des C ++ - Operators ab . Grundsätzlich erstellt der Compiler in Ausdrücken mit mehreren Operatoren und ohne Klammern den Ausdrucksbaum, indem er diese Regeln befolgt.

Wenn Sie so etwas haben A op1 B op2 C, können Sie die Dinge vorrangig entweder (A op1 B) op2 Coder gruppieren A op1 (B op2 C). Wenn es op1eine höhere Priorität als hat op2, erhalten Sie den ersten Ausdruck. Andernfalls erhalten Sie den zweiten.

Aus A op B op CGründen der Assoziativität können Sie, wenn Sie so etwas haben , die Ausdünnungen erneut als (A op B) op Coder gruppieren A op (B op C). Wenn opdie Assoziativität verlassen wurde, erhalten wir den ersten Ausdruck. Wenn es die richtige Assoziativität hat, erhalten wir die zweite. Dies funktioniert auch für Bediener mit derselben Prioritätsebene.

In diesem speziellen Fall &&hat eine höhere Priorität als ||, sodass der Ausdruck als ausgewertet wird (a != "" && it == seqMap.end()) || isEven.

Die Reihenfolge selbst ist in der Ausdrucksbaumform "von links nach rechts". Also werden wir zuerst bewerten a != "" && it == seqMap.end(). Wenn es wahr ist, ist der ganze Ausdruck wahr, sonst gehen wir zu isEven. Die Prozedur wiederholt sich natürlich rekursiv innerhalb des linken Unterausdrucks.


Interessante Leckerbissen, aber das Konzept der Vorrangstellung hat seine Wurzeln in der mathematischen Notation. Das gleiche passiert in a*b + c, wo *hat höhere Priorität als +.

Noch interessanter / dunkler ist, dass für einen nicht parenthasierten Ausdruck A1 op1 A2 op2 ... opn-1 An, bei dem alle Operatoren dieselbe Priorität haben, die Anzahl der binären Ausdrucksbäume, die wir bilden könnten, durch die sogenannten katalanischen Zahlen angegeben wird . Für große nwachsen diese extrem schnell. d

Horia Coman
quelle
All dies ist richtig, aber es geht um Vorrang und Assoziativität des Operators, nicht um Auswertungsreihenfolge und Kurzschluss. Das sind verschiedene Dinge.
Thomas Padron-McCarthy
0

Wenn Sie Wikipedia vertrauen:

[ &&und ||] unterscheiden sich semantisch von den bitweisen Operatoren & und | weil sie niemals den rechten Operanden auswerten, wenn das Ergebnis allein von links bestimmt werden kann

C (Programmiersprache)

Sophie Alpert
quelle
11
Warum dem Wiki vertrauen, wenn wir einen Standard haben?
Martin York
1
Wenn Sie Wikipedia vertrauen, ‚ist Wikipedia nicht eine zuverlässige Quelle‘ .
Marquis von Lorne
Dies gilt zwar, ist jedoch unvollständig, da überladene Operatoren in C ++ nicht kurzgeschlossen werden.
Thomas Padron-McCarthy