Warum zeigt diese Version von logischem UND in C kein Kurzschlussverhalten?

80

Ja, dies ist eine Hausaufgabenfrage, aber ich habe meine Nachforschungen angestellt und ziemlich viel über das Thema nachgedacht und kann dies nicht herausfinden. Die Frage besagt, dass dieser Code KEIN Kurzschlussverhalten aufweist und fragt, warum. Aber es sieht für mich so aus, als ob es ein Kurzschlussverhalten zeigt. Kann jemand erklären, warum dies nicht der Fall ist?

In C:

int sc_and(int a, int b) {
    return a ? b : 0;
}

Es sieht für mich so aus a, als würde das Programm im falschen Fall überhaupt nicht versuchen, eine Bewertung bvorzunehmen, aber ich muss mich irren. Warum berührt das Programm bin diesem Fall überhaupt, wenn es nicht muss?

Bobazonski
quelle
11
Wie es für die meisten erfundenen Hausaufgaben typisch ist, werden Sie diese Art von Code in einem Produktionssystem nie sehen, es sei denn, der Programmierer versucht absichtlich, klug zu sein.
Robert Harvey
49
@RobertHarvey In Produktionssystemen wird immer genau so Code angezeigt! Es ist wahrscheinlich keine Funktion mit dem Namen AND(), aber Funktionen, die Argumente nach Wert empfangen und sie dann auswerten (oder nicht), abhängig von der Logik der Funktion, sind überall. Obwohl es sich um eine "Trickfrage" handelt, ist es ein kritisches Verhalten von C, dies zu verstehen. In einer Sprache mit Call-by-Name hätte diese Frage eine ganz andere Antwort.
Ben Jackson
7
@ BenJackson: Ich habe den Code kommentiert, nicht das Verhalten. Ja, Sie müssen das Verhalten verstehen. Nein, Sie müssen keinen solchen Code schreiben.
Robert Harvey
3
Dies ist tatsächlich sehr relevant, wenn Sie jemals in VB codieren und auf IIf stoßen müssen. Da es sich eher um eine Funktion als um einen Operator handelt, wird die Auswertung nicht kurzgeschlossen. Dies kann zu Problemen für Entwickler führen, die an den Kurzschlussoperator gewöhnt sind und dann Dinge wie schreiben IIf(x Is Nothing, Default, x.SomeValue).
Dan Bryant
3
@alk, weil es eine Anklage gegen das Bildungssystem ist.
Shivan Dragon

Antworten:

118

Dies ist eine Trickfrage. bist ein Eingabeargument für die sc_andMethode und wird daher immer ausgewertet. Mit anderen Worten, es sc_and(a(), b())wird angerufen a()und angerufen b()(Reihenfolge nicht garantiert), und dann angerufen, sc_anddessen Ergebnisse a(), b()an weitergeleitet werden a?b:0. Es hat nichts mit dem ternären Operator selbst zu tun, der absolut kurzschließen würde.

AKTUALISIEREN

In Bezug darauf, warum ich dies als "Trickfrage" bezeichnet habe: Es liegt an dem Mangel an genau definierten Zusammenhängen, in denen "Kurzschluss" (zumindest wie vom OP reproduziert) zu berücksichtigen ist. Viele Personen gehen, wenn sie nur eine Funktionsdefinition erhalten, davon aus, dass der Kontext der Frage nach dem Körper der Funktion fragt ; Sie betrachten die Funktion oft nicht als Ausdruck an und für sich. Dies ist der "Trick" der Frage; Um Sie daran zu erinnern, dass Sie beim Programmieren im Allgemeinen, aber insbesondere in Sprachen wie C-Likes, die oft viele Ausnahmen von Regeln haben, dies nicht tun können. Beispiel, wenn die Frage als solche gestellt wurde:

Betrachten Sie den folgenden Code. Zeigt sc_and ein Kurzschlussverhalten an, wenn es von main aufgerufen wird :

int sc_and(int a, int b){
    return a?b:0;
}

int a(){
    cout<<"called a!"<<endl;
    return 0;
}

int b(){
    cout<<"called b!"<<endl;
    return 1;
}

int main(char* argc, char** argv){
    int x = sc_and(a(), b());
    return 0;
}

Es wäre sofort klar, dass Sie sc_andals Operator an und für sich in Ihrer eigenen domänenspezifischen Sprache denken und bewerten sollten, ob der Aufruf, ein sc_andKurzschlussverhalten wie ein normaler &&zu zeigen . Ich würde das überhaupt nicht als Trickfrage betrachten, da klar ist, dass Sie sich nicht auf den ternären Operator konzentrieren sollten, sondern sich stattdessen auf die Funktionsaufrufmechanik von C / C ++ konzentrieren sollten (und ich würde vermuten, führen schön in eine Folgefrage zu schreiben sc_and, die einen Kurzschluss macht, der die Verwendung einer #definestatt einer Funktion beinhalten würde).

Ob Sie das, was der ternäre Operator selbst kurzschließt (oder etwas anderes, wie "bedingte Bewertung"), aufrufen oder nicht, hängt von Ihrer Definition des Kurzschlusses ab, und Sie können die verschiedenen Kommentare lesen, um darüber nachzudenken. Meins schon, aber es ist nicht besonders relevant für die eigentliche Frage oder warum ich es einen "Trick" genannt habe.

aruisdante
quelle
13
Der ternäre Operator macht einen Kurzschluss? Nein. Es wertet einen der folgenden Ausdrücke aus ?, genau wie in if (condition) {when true} else {when-false}. Das nennt man nicht Kurzschluss.
Jens
17
@Jens: Ich würde jeden Fall, in dem ein Operator die Auswertung eines oder mehrerer seiner Operanden überspringt, als eine Form des "Kurzschlusses" bezeichnen, aber dies ist wirklich nur eine Frage der Art und Weise, wie Sie den Begriff definieren.
R .. GitHub STOP HELPING ICE
13
@Jens Es ist syntaktischer Zucker für einen if else, nicht für einen if. Und selbst dann ist es nicht wirklich; Der ?:Operator ist ein Ausdruck , if-elseeine Anweisung . Das sind semantisch sehr unterschiedliche Dinge, selbst wenn Sie eine äquivalente Menge von Anweisungen erstellen können, die den gleichen Effekt wie der ternäre Ausdruck erzeugen . Aus diesem Grund wird es als Kurzschluss angesehen. Die meisten anderen Ausdrücke bewerten immer alle ihre Operanden ( +,-,*,/usw.). Wenn nicht, ist es ein Kurzschluss ( &&, ||).
aruisdante
9
Ja, für mich ist der wichtige Unterschied, dass es sich um Operatoren handelt. Natürlich steuern Flusssteuerungsanweisungen, welcher Codepfad ausgeführt wird. Für Operatoren ist es für jemanden, der nicht mit C- und C-abgeleiteten Sprachen vertraut ist, nicht offensichtlich, dass ein Operator möglicherweise nicht alle seine Operanden auswertet. Daher ist es wichtig, einen Begriff zu haben, um über diese Eigenschaft zu sprechen, und dafür verwende ich "short" Schaltung ".
R .. GitHub STOP HELPING ICE
43

Wenn die Aussage

bool x = a && b++;  // a and b are of int type

wird ausgeführt, b++wird nicht ausgewertet, wenn der Operand aausgewertet wird false(Kurzschlussverhalten). Dies bedeutet, dass die Nebenwirkung bnicht auftritt.

Schauen Sie sich nun die Funktion an:

bool and_fun(int a, int b)
{
     return a && b; 
}

und nenne das

bool x = and_fun(a, b++);

In diesem Fall , ob aist trueoder false, b++immer ausgewertet wird 1 bei Funktionsaufruf und Nebenwirkung auf bPlatz wird immer nehmen.

Gleiches gilt für

int x = a ? b : 0; // Short circuit behavior 

und

int sc_and (int a, int b) // No short circuit behavior.
{
   return a ? b : 0;
} 

1 Die Reihenfolge der Bewertung von Funktionsargumenten ist nicht angegeben.

Haccks
quelle
1
OK, also mit Stephen Quans Antwort übereinstimmend: Ist es möglich (legal), dass ein Compilar die Funktion "and_fun" einbindet, so dass beim Aufrufen von "bool x = and_fun (a, b ++)"; b ++ wird nicht erhöht, wenn a wahr ist?
Shivan Dragon
4
@ShivanDragon Das klingt für mich nach einer Änderung des beobachtbaren Verhaltens.
Sapi
3
@ShivanDragon: Beim Inlining ändert der Compiler das Verhalten nicht. Es ist nicht so einfach wie das Ersetzen des Funktionskörpers in.
Jack Aidley
Zur Verdeutlichung können Sie int x = a ? b++ : 0als beobachtbaren Kurzschluss hinzufügen .
Paul Draper
@PaulDraper; Ich habe das Original-Snippet behalten, da es für die Leser nicht verwirrend ist.
Haccks
19

Wie bereits von anderen erwähnt, wird unabhängig davon, was als die beiden Argumente in die Funktion übergeben wird, diese ausgewertet, sobald sie übergeben wird. Das ist weit vor der Tenary-Operation.

Auf der anderen Seite dies

#define sc_and(a, b) \
  ((a) ?(b) :0)

würde "kurzschließen", da dieses Makro keinen Funktionsaufruf impliziert und damit keine Bewertung der Argumente einer Funktion durchgeführt wird.

alk
quelle
vielleicht erklären warum? Es sieht für mich genauso aus wie das Snippet der Operation. Nur dass seine Inline nicht einem anderen Bereich entspricht.
Dhein
5

Bearbeitet, um die im Kommentar von @cmasters angegebenen Fehler zu korrigieren.


Im

int sc_and(int a, int b) {
    return a ? b : 0;
}

... der returnAusdruck ed zeigt eine Kurzschlussauswertung, der Funktionsaufruf jedoch nicht.

Versuchen Sie anzurufen

sc_and (0, 1 / 0);

Der Funktionsaufruf wird ausgewertet 1 / 0, obwohl er nie verwendet wird, was wahrscheinlich zu einem Fehler beim Teilen durch Null führt.

Relevante Auszüge aus dem (Entwurf) des ANSI C-Standards sind:

2.1.2.3 Programmausführung

...

In der abstrakten Maschine werden alle Ausdrücke gemäß der Semantik ausgewertet. Eine tatsächliche Implementierung muss keinen Teil eines Ausdrucks auswerten, wenn daraus geschlossen werden kann, dass sein Wert nicht verwendet wird und keine erforderlichen Nebenwirkungen auftreten (einschließlich solcher, die durch den Aufruf einer Funktion oder den Zugriff auf ein flüchtiges Objekt verursacht werden).

und

3.3.2.2 Funktionsaufrufe

....

Semantik

...

Bei der Vorbereitung des Aufrufs einer Funktion werden die Argumente ausgewertet und jedem Parameter der Wert des entsprechenden Arguments zugewiesen.

Ich vermute, dass jedes Argument als Ausdruck ausgewertet wird, die gesamte Argumentliste jedoch kein Ausdruck ist, weshalb das Nicht-SCE-Verhalten obligatorisch ist.

Als Spritzer auf der Oberfläche der tiefen Gewässer des C-Standards würde ich eine gut informierte Sicht auf zwei Aspekte begrüßen:

  • Erzeugt die Bewertung 1 / 0undefiniertes Verhalten?
  • Ist eine Argumentliste ein Ausdruck? (Ich denke nicht)

PS

Selbst wenn Sie zu C ++ wechseln und sc_andals inlineFunktion definieren , erhalten Sie keine SCE. Wenn Sie es wie @alk als C-Makro definieren , werden Sie es sicherlich tun .

Miniaturansicht
quelle
1
Nein, eine inlineFunktion ändert die Semantik eines Funktionsaufrufs nicht. Und geben Sie diese Semantik , dass alle Argumente werden ausgewertet, wie Sie richtig zitiert. Selbst wenn der Compiler den Aufruf optimieren kann, sc_and(f(), g())darf sich das sichtbare Verhalten nicht ändern, dh es muss sich so verhalten, als ob beide f()und g()immer aufgerufen werden. sc_and(0, 1/0)ist ein schlechtes Beispiel , weil es ist nicht definiertes Verhalten, und der Compiler ist nicht erforderlich , um auch Call sc_and()...
cMaster - wieder einzusetzen monica
@ Master Danke. Ich wusste einfach nicht, dass C ++ inlinedie Aufrufsemantik beibehält. Ich habe mich an die Nullteilung gehalten, wie es zum Beispiel passt, und SCE wird meiner Meinung nach oft ausgenutzt, um undefiniertes Verhalten zu vermeiden .
Vorschaubild
4

Um ternäre Kurzschlüsse deutlich zu erkennen, ändern Sie den Code geringfügig, um Funktionszeiger anstelle von Ganzzahlen zu verwenden:

int a() {
    printf("I'm a() returning 0\n");
    return 0;
}

int b() {
    printf("And I'm b() returning 1 (not that it matters)\n");
    return 1;
}

int sc_and(int (*a)(), int (*b)()) {
    a() ? b() : 0;
}

int main() {
    sc_and(a, b);
    return 0;
}

Und dann kompilieren Sie es (auch mit fast KEINER Optimierung : -O0!). Sie werden sehen, dass b()es nicht ausgeführt wird, wenn a()false zurückgegeben wird.

% gcc -O0 tershort.c            
% ./a.out 
I'm a() returning 0
% 

Hier sieht die generierte Baugruppe folgendermaßen aus:

    call    *%rdx      <-- call a()
    testl   %eax, %eax <-- test result
    je      .L8        <-- skip if 0 (false)
    movq    -16(%rbp), %rdx
    movl    $0, %eax
    call    *%rdx      <- calls b() only if not skipped
.L8:

Wie andere richtig betonten, besteht der Fragetrick darin, dass Sie sich auf das ternäre Operatorverhalten konzentrieren, das einen Kurzschluss verursacht (nennen Sie dies 'bedingte Auswertung'), anstatt auf die Parameterauswertung beim Aufruf (Aufruf nach Wert), die NICHT kurzschließt.

Flaviodesousa
quelle
0

Der ternäre Operator C kann niemals einen Kurzschluss verursachen, da er nur einen einzelnen Ausdruck a (die Bedingung) auswertet , um einen durch die Ausdrücke b und c gegebenen Wert zu bestimmen , falls ein Wert zurückgegeben werden könnte.

Der folgende Code:

int ret = a ? b : c; // Here, b and c are expressions that return a value.

Es entspricht fast dem folgenden Code:

int ret;
if(a) {ret = b} else {ret = c}

Der Ausdruck a kann von anderen Operatoren wie && oder || gebildet werden Dies kann einen Kurzschluss verursachen, da sie möglicherweise zwei Ausdrücke auswerten, bevor sie einen Wert zurückgeben. Dies wird jedoch nicht als ternärer Kurzschlussoperator angesehen, sondern als Operator, der in der Bedingung wie in einer regulären if-Anweisung verwendet wird.

Aktualisieren:

Es gibt einige Debatten darüber, dass der ternäre Operator ein Kurzschlussoperator ist. Das Argument besagt, dass jeder Operator, der nicht alle Operanden auswertet, gemäß @aruisdante im Kommentar unten einen Kurzschluss macht. Wenn diese Definition gegeben wäre, würde der ternäre Operator kurzschließen, und in dem Fall, dass dies die ursprüngliche Definition ist, stimme ich zu. Das Problem ist, dass der Begriff "Kurzschluss" ursprünglich für eine bestimmte Art von Operator verwendet wurde, der dieses Verhalten zuließ, und dies sind die logischen / booleschen Operatoren, und der Grund, warum nur diese Operatoren vorhanden sind, werde ich zu erklären versuchen.

Nach dem Artikel Kurzschlussbewertung bezieht sich die Kurzschlussbewertung nur auf boolesche Operatoren, die in der Sprache so implementiert sind, dass das Wissen, dass der erste Operand den zweiten irrelevant macht, dies bedeutet, dass der Operator && der erste Operand falsch ist und für die || Da der Operator der erste wahre Operand ist , wird er in der C11-Spezifikation auch in 6.5.13 Logischer UND-Operator und 6.5.14 Logischer ODER-Operator angegeben.

Dies bedeutet, dass Sie das zu identifizierende Kurzschlussverhalten in einem Operator identifizieren müssen, der alle Operanden genau wie die booleschen Operatoren auswerten muss, wenn der erste Operand den zweiten nicht irrelevant macht. Dies steht im Einklang mit dem, was in einer anderen Definition für den Kurzschluss in MathWorks im Abschnitt "Logischer Kurzschluss" geschrieben ist, da der Kurzschluss von den logischen Operatoren stammt.

Da ich versucht habe, den C-ternären Operator zu erklären, der auch als ternär bezeichnet wird, wenn er nur zwei der Operanden auswertet, wertet er den ersten aus und wertet dann einen zweiten aus, wobei einer der beiden verbleibenden abhängig vom Wert des Erster. Es tut dies immer, es soll in keiner Situation alle drei bewerten, also gibt es auf keinen Fall einen "Kurzschluss".

Wie immer, wenn Sie sehen, dass etwas nicht stimmt, schreiben Sie bitte einen Kommentar mit einem Argument dagegen und nicht nur einer Abwertung, die die SO-Erfahrung nur verschlimmert, und ich glaube, wir können eine viel bessere Community sein als eine, die nur die Antworten ablehnt man stimmt nicht zu.

Adrián Pérez
quelle
3
Und warum das Downvote? Ich hätte gerne Kommentare, damit ich alles korrigieren kann, was ich falsch gesagt habe. Bitte seien Sie konstruktiv.
Adrián Pérez
2
Vielleicht -1, weil es nicht 100% zum Thema ist. aber trotzdem habe ich +1 gegeben, weil deine Antwort es zumindest als statemant erklärt und es nicht falsch zu sein scheint.
Dhein
4
@AdrianPerez siehe den Kommentarbereich in meiner Antwort, warum die meisten Leute den terniären Operator zu 100% als Kurzschluss betrachten würden. Es ist ein Ausdruck, der nicht alle Operanden basierend auf dem Wert einiger seiner Operanden bewertet. Das ist ein Kurzschluss.
aruisdante
3
Ich kann mir keinen nicht-booleschen Operator vorstellen, der nicht alle Operanden auswertet. Aber das ist nicht der Punkt; Der Grund, warum es als Kurzschluss bezeichnet wird, ist genau, weil es ein Operator (oder im allgemeinen Sinne eher ein Ausdruck als eine Aussage) ist, der nicht alle seine Operanden bewertet. Der Typ des Bedieners spielt keine Rolle. Es wäre durchaus möglich, boolesche Operatoren zu schreiben, die nicht kurzgeschlossen wurden, und Sie könnten auch nicht-boolesche Operatoren schreiben, die dies auch taten (Beispiel: Die ganzzahlige Multiplikation von 0 ist immer 0, geben Sie also sofort 0 zurück, wenn der erste Operand 0 ist).
aruisdante
2
x = a ? b : 0;ist semantisch dasselbe wie(a && (x = b)) || (x = 0);
jxh