Switch-Anweisung: muss default der letzte Fall sein?

Lesezeit: 11 Minuten

Benutzeravatar von tanascius
Tanaszius

Folgendes berücksichtigen switch Aussage:

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

Dieser Code lässt sich kompilieren, aber ist er gültig (= definiertes Verhalten) für C90/C99? Ich habe noch nie Code gesehen, wo die Ursprünglich Fall ist nicht der letzte Fall.

BEARBEITEN:

Wie Jon Käfig und KillianDS schreiben: Das ist wirklich hässlicher und verwirrender Code und ich bin mir dessen bewusst. Ich interessiere mich nur für die allgemeine Syntax (ist sie definiert?) und die erwartete Ausgabe.

  • +1 Ich habe dieses Verhalten nie in Betracht gezogen

    – Jamie Wong

    24. Juni 2010 um 12:59 Uhr

  • @Péter Török nein, die Reihenfolge spielt keine Rolle – wenn der Wert auf jeden Fall mit dem Label der Konstante übereinstimmt, springt die Steuerung zu dieser Anweisung nach dem Label, andernfalls springt die Steuerung zu der Anweisung nach dem Standardlabel, falls vorhanden.

    – Peter Kirkham

    24. Juni 2010 um 13:10 Uhr

  • @Jon Cage goto ist nicht böse. Cargo-Kult-Anhänger sind! Sie können sich nicht vorstellen, zu welchen Extremen Menschen gehen können, um sie zu vermeiden goto weil es angeblich so böse ist, dass es ihren Code wirklich unlesbar macht.

    – Patrick Schlüter

    24. Juni 2010 um 13:34 Uhr

  • Vielleicht evil war vielleicht eine schlechte Wortwahl easily abused oder easily misunderstood. Es gibt keinen Grund, warum das Vermeiden von goto-Anforderungen zu weniger lesbarem Code führen sollte, und im Allgemeinen würde ich vorschlagen, wenn Sie goto-Anforderungen benötigen, dass dies ein Zeichen dafür ist, dass der Code-Code von vornherein besser strukturiert ist. Wenn Sie konkrete Beispiele haben, die meinen Standpunkt widerlegen, wäre ich sehr daran interessiert, sie zu sehen. Ich schimpfe hier nicht, ich musste in der Vergangenheit nur einige böse Fehler beheben, die durch gotos und ähnliche Sprachkonstrukte verursacht wurden 🙂

    – Jon Cage

    24. Juni 2010 um 13:57 Uhr

  • ich benutze goto hauptsächlich, um so etwas wie a zu simulieren finally Klausel in Funktionen, wo Ressourcen (Dateien, Speicher) beim Stoppen freigegeben werden müssen, und Wiederholung für jeden Fehlerfall einer Liste von free und close trägt nicht zur Lesbarkeit bei. Es gibt jedoch eine Verwendung von goto das ich gerne vermeiden möchte, aber nicht kann, ist, wenn ich aus einer Schleife ausbrechen möchte und mich in einer befinde switch in dieser Schleife.

    – Patrick Schlüter

    24. Juni 2010 um 14:18 Uhr

Salils Benutzeravatar
Salil

Die case-Anweisungen und die default-Anweisung können in der switch-Anweisung in beliebiger Reihenfolge vorkommen. Die Standardklausel ist eine optionale Klausel, die abgeglichen wird, wenn keine der Konstanten in den case-Anweisungen abgeglichen werden kann.

Gutes Beispiel :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

sehr nützlich, wenn Sie möchten, dass Ihre Fälle in einer logischen Reihenfolge im Code dargestellt werden (wie in Fall 1, Fall 3, Fall 2/Standard) und Ihre Fälle sehr lang sind, sodass Sie nicht den gesamten Fall wiederholen möchten Code unten für die Standardeinstellung

  • Dies ist genau das Szenario, in dem ich den Standard normalerweise an einer anderen Stelle als am Ende platziere … die expliziten Fälle (1, 2, 3) haben eine logische Reihenfolge, und ich möchte, dass sich der Standard genau so verhält wie einer der expliziten Fälle ist nicht das letzte.

    – ArtOfWarfare

    18. Dezember 2012 um 16:10 Uhr

Benutzeravatar von Secure
Sicher

Der C99-Standard ist diesbezüglich nicht explizit, aber wenn man alle Fakten zusammennimmt, ist er vollkommen gültig.

EIN case und default Label sind äquivalent zu a goto Etikett. Siehe 6.8.1 Beschriftete Aussagen. Besonders interessant ist 6.8.1.4, das das bereits erwähnte Duff’s Device aktiviert:

Jeder Anweisung kann ein Präfix vorangestellt werden, das einen Bezeichner als Markennamen deklariert. Labels an sich verändern nicht den Kontrollfluss, der sich ungehindert über sie hinweg fortsetzt.

Bearbeiten: Der Code innerhalb eines Schalters ist nichts Besonderes; es ist ein normaler Codeblock wie in an if-Anweisung, mit zusätzlichen Sprungmarken. Dies erklärt das Fall-Through-Verhalten und warum break ist notwendig.

6.8.4.2.7 gibt sogar ein Beispiel:

switch (expr) 
{ 
    int i = 4; 
    f(i); 
case 0: 
    i=17; 
    /*falls through into default code */ 
default: 
    printf("%d\n", i); 
} 

In dem künstlichen Programmfragment existiert das Objekt, dessen Bezeichner i ist, mit automatischer Speicherdauer (innerhalb des Blocks), wird aber nie initialisiert, und daher greift der Aufruf der printf-Funktion auf einen unbestimmten Wert zu, wenn der steuernde Ausdruck einen Wert ungleich Null hat. Ebenso ist der Aufruf der Funktion f nicht erreichbar.

Die case-Konstanten müssen innerhalb einer switch-Anweisung eindeutig sein:

6.8.4.2.3 Der Ausdruck jedes case-Labels muss ein ganzzahliger konstanter Ausdruck sein und keine zwei der case-konstanten Ausdrücke in derselben switch-Anweisung dürfen nach der Konvertierung denselben Wert haben. In einer switch-Anweisung darf es höchstens ein Default-Label geben.

Alle Fälle werden ausgewertet, dann springt es zum Default-Label, falls vorhanden:

6.8.4.2.5 Die Integer-Promotions werden für den steuernden Ausdruck durchgeführt. Der konstante Ausdruck in jedem Fall Label wird in den geförderten Typ des steuernden Ausdrucks konvertiert. Wenn ein konvertierter Wert mit dem des heraufgestuften Steuerausdrucks übereinstimmt, springt die Steuerung zu der Anweisung, die auf die übereinstimmende Groß-/Kleinschreibung folgt. Andernfalls springt die Steuerung, wenn es eine Standardbezeichnung gibt, zur beschrifteten Anweisung. Wenn kein konvertierter Case-Konstantenausdruck übereinstimmt und es keine Standardbezeichnung gibt, wird kein Teil des Switch-Hauptteils ausgeführt.

  • @HeathHunnicutt Sie haben den Zweck des Beispiels eindeutig nicht verstanden. Der Code stammt nicht von diesem Poster, sondern stammt direkt aus dem C-Standard, um zu veranschaulichen, wie seltsam switch-Anweisungen sind und wie schlechte Praxis zu Fehlern führt. Wenn Sie sich die Mühe gemacht hätten, den Text unter dem Code zu lesen, würden Sie das erkennen.

    – Ludin

    13. November 2013 um 14:49 Uhr


  • +1, um die Ablehnung zu kompensieren. Jemanden vom Zitieren des C-Standards herabzustimmen, scheint ziemlich hart zu sein.

    – Ludin

    13. November 2013 um 14:50 Uhr


  • @Lundin Ich stimme dem C-Standard nicht ab und habe nichts übersehen, wie Sie vorschlagen. Ich habe die schlechte Pädagogik, ein schlechtes und unnötiges Beispiel zu verwenden, abgelehnt. Insbesondere bezieht sich dieses Beispiel auf eine völlig andere Situation als die, nach der gefragt wurde. Ich könnte weitermachen, aber “Danke für Ihr Feedback.”

    – Heide Hunnicutt

    13. November 2013 um 15:34 Uhr

  • Intel sagt Ihnen, dass Sie den häufigsten Code zuerst in einer switch-Anweisung unter platzieren sollen Verzweigungs- und Loop-Reorganisation zur Vermeidung von Fehlvorhersagen. Ich bin hier, weil ich eine habe default Fall dominiert andere Fälle um etwa 100: 1, und ich weiß nicht, ob es gültig oder undefiniert ist default der erste Fall.

    – jww

    28. September 2016 um 3:46 Uhr


  • @JaveneCPPMcGowan Ich bin mir sicher, dass mit Intel jww die Firma gemeint ist, die Computerchips herstellt, nicht das gebräuchliche Wort Intelligenz. Und Ihr Denken ist nicht ganz richtig: Der Compiler nimmt sich die Freiheit, einige Switch-Fälle in parametrisierte (indirektes Register) unbedingtes jmp umzuwandeln und andere Switch-Fälle in bedingte Verzweigungen umzuwandeln.

    – 把友情留在无盐

    11. Januar 2020 um 4:31 Uhr

Benutzeravatar von kriss
kriss

Es ist gültig und in einigen Fällen sehr nützlich.

Betrachten Sie den folgenden Code:

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

Der Punkt ist, dass der obige Code lesbarer und effizienter ist als kaskadiert if. Du könntest setzen default am Ende, aber es ist sinnlos, da es Ihre Aufmerksamkeit auf Fehlerfälle anstatt auf normale Fälle lenkt (was hier die ist default Fall).

Eigentlich ist es kein so gutes Beispiel, in poll Sie wissen, wie viele Ereignisse maximal auftreten können. Mein eigentlicher Punkt ist, dass dort sind Fälle mit einem definierten Satz von Eingabewerten, in denen es „Ausnahmen“ und Normalfälle gibt. Ob es besser ist, Ausnahmen oder Normalfälle voranzustellen, ist eine Frage der Wahl.

Im Softwarebereich denke ich an einen anderen sehr üblichen Fall: Rekursionen mit einigen Endwerten. Wenn Sie es mit einem Schalter ausdrücken können, default Dabei handelt es sich um den üblichen Wert, der rekursive Aufrufe und Unterscheidungselemente (Einzelfälle) der Endwerte enthält. In der Regel besteht keine Notwendigkeit, sich auf Endwerte zu konzentrieren.

Ein weiterer Grund ist, dass die Reihenfolge der Fälle das Verhalten des kompilierten Codes ändern kann, und das ist für die Leistung von Bedeutung. Die meisten Compiler generieren kompilierten Assemblercode in derselben Reihenfolge, in der der Code im Switch angezeigt wird. Das unterscheidet den ersten Fall sehr von den anderen: Alle Fälle außer dem ersten beinhalten einen Sprung und das wird die Prozessor-Pipelines leeren. Sie können es wie eine Verzweigungsvorhersage verstehen, die standardmäßig den ersten auftretenden Fall im Schalter ausführt. Wenn ein Fall viel häufiger auftritt als die anderen, dann haben Sie sehr gute Gründe, ihn als ersten Fall zu bezeichnen.

Das Lesen von Kommentaren ist der spezifische Grund, warum der ursprüngliche Poster diese Frage nach dem Lesen gestellt hat Neuorganisation des Intel-Compilers Branch Loop über Code-Optimierung.

Dann wird es zu einer Art Arbitrierung zwischen Codelesbarkeit und Codeleistung. Wahrscheinlich besser, einen Kommentar zu setzen, um zukünftigen Lesern zu erklären, warum ein Fall zuerst erscheint.

  • +1 für ein (gutes) Beispiel ohne das Fallthrough-Verhalten.

    – KillianDS

    24. Juni 2010 um 13:30 Uhr

  • … Wenn ich darüber nachdenke, bin ich jedoch nicht davon überzeugt, dass es gut ist, den Standardwert oben zu haben, da nur sehr wenige Leute dort danach suchen würden. Es könnte besser sein, die Rückgabe einer Variablen zuzuweisen und den Erfolg auf der einen Seite eines if und Fehler auf der anderen Seite mit einer case-Anweisung zu behandeln.

    – Jon Cage

    24. Juni 2010 um 13:43 Uhr

  • @Jon: schreib es einfach. Sie fügen syntaktisches Rauschen ohne Lesbarkeitsvorteil hinzu. Und wenn default ganz oben steht, muss man es sich wirklich nicht ansehen, es ist wirklich offensichtlich (es könnte kniffliger sein, wenn man es in die Mitte setzt).

    – kriss

    24. Juni 2010 um 14:27 Uhr

  • Übrigens mag ich die C-Switch/Case-Syntax nicht wirklich. Ich würde es vorziehen, mehrere Etiketten hinter einem Fall anbringen zu können, anstatt mehrere hintereinander anbringen zu müssen case. Was deprimierend ist, ist, dass es wie syntaktischer Zucker aussieht und keinen bestehenden Code bricht, wenn er unterstützt wird.

    – kriss

    24. Juni 2010 um 14:57 Uhr

  • @kriss: Ich war fast versucht zu sagen: “Ich bin auch kein Python-Programmierer!” 🙂

    – Andreas Grimm

    5. November 2010 um 12:05 Uhr

ja, das ist gültig und unter Umständen sogar sinnvoll. Im Allgemeinen, wenn Sie es nicht brauchen, tun Sie es nicht.

Benutzeravatar von Patrick Schlüter
Patrick Schlüter

Es gibt keine definierte Reihenfolge in einer switch-Anweisung. Sie können die Fälle als so etwas wie ein benanntes Etikett betrachten, wie z goto Etikett. Im Gegensatz zu dem, was die Leute hier zu denken scheinen, wird im Fall von Wert 2 nicht auf das Standardlabel gesprungen. Zur Veranschaulichung mit einem klassischen Beispiel, hier ist Duffs Gerätdas das Aushängeschild der Extreme von ist switch/case in C.

send(to, from, count)
register short *to, *from;
register count;
{
  register n=(count+7)/8;
  switch(count%8){
    case 0: do{ *to = *from++;
    case 7:     *to = *from++;
    case 6:     *to = *from++;
    case 5:     *to = *from++;
    case 4:     *to = *from++;
    case 3:     *to = *from++;
    case 2:     *to = *from++;
    case 1:     *to = *from++;
            }while(--n>0);
  }
}

  • Und für jeden, der mit Duffs Gerät nicht vertraut ist, ist dieser Code völlig unlesbar …

    – KillianDS

    24. Juni 2010 um 13:34 Uhr


  • Duff’s Device ist ein perfektes Beispiel für “Nur weil du es kannst, heißt das nicht, dass du es solltest”

    – Andreas

    10. Oktober 2021 um 11:55 Uhr

Benutzeravatar von Matias Kinnunen
Matias Kinnunen

Ein Szenario, in dem ich es für angemessen halten würde, das zu haben default Ein Fall, der sich an einer anderen Stelle als am Ende einer switch-Anweisung befindet, befindet sich in einer Zustandsmaschine, wo ein ungültiger Zustand die Maschine zurücksetzen und so fortfahren sollte, als wäre es der Anfangszustand. Zum Beispiel:

switch(widget_state)
{
  default:  /* Fell off the rails--reset and continue */
    widget_state = WIDGET_START;
    /* Fall through */
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
}

Eine alternative Anordnung, wenn ein ungültiger Zustand die Maschine nicht zurücksetzen soll, aber leicht als ungültiger Zustand erkennbar sein soll:

switch(widget_state)
{
  case WIDGET_IDLE:
    widget_ready = 0;
    widget_hardware_off();
    break;
  case WIDGET_START:
    ...
    break;
  case WIDGET_WHATEVER:
    ...
    break;
  default:
    widget_state = WIDGET_INVALID_STATE;
    /* Fall through */
  case WIDGET_INVALID_STATE:
    widget_ready = 0;
    widget_hardware_off();
    ... do whatever else is necessary to establish a "safe" condition
}

Code an anderer Stelle kann dann prüfen widget_state == WIDGET_INVALID_STATE und bieten, was auch immer für ein Fehlerberichts- oder Zustandsrücksetzungsverhalten angemessen erscheint. Beispielsweise könnte der Status-Strichcode ein Fehler-Icon anzeigen, und die Menüoption “Widget starten”, die in den meisten Nicht-Leerlaufzuständen deaktiviert ist, könnte aktiviert werden WIDGET_INVALID_STATE ebenso gut wie WIDGET_IDLE.

  • Und für jeden, der mit Duffs Gerät nicht vertraut ist, ist dieser Code völlig unlesbar …

    – KillianDS

    24. Juni 2010 um 13:34 Uhr


  • Duff’s Device ist ein perfektes Beispiel für “Nur weil du es kannst, heißt das nicht, dass du es solltest”

    – Andreas

    10. Oktober 2021 um 11:55 Uhr

Benutzeravatar von Brennan Vincent
Brennan Vincent

Ein weiteres Beispiel: Dies kann nützlich sein, wenn “default” ein unerwarteter Fall ist und Sie den Fehler protokollieren, aber auch etwas Vernünftiges tun möchten. Beispiel aus meinem eigenen Code:

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }

1425730cookie-checkSwitch-Anweisung: muss default der letzte Fall sein?

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

Privacy policy