Ausbrechen aus einer Schleife innerhalb einer Funktion, die in dieser Schleife aufgerufen wird

Lesezeit: 13 Minuten

Benutzer-Avatar
Magisch

Ich versuche gerade, einen Weg zu finden, um aus einem auszubrechen for Schleife innerhalb einer Funktion, die in dieser Schleife aufgerufen wird. Ich bin mir der Möglichkeit bewusst, die Funktion nur einen Wert zurückgeben zu lassen und dann einen bestimmten Wert zu prüfen und dann zu unterbrechen, aber ich möchte dies direkt aus der Funktion heraus tun.

Dies liegt daran, dass ich eine interne Bibliothek für eine bestimmte Hardware verwende, die die Funktionssignatur meiner Funktion so aussehen lässt:

void foo (int passV, int aVal, long bVal)

Mir ist bewusst, dass kein Rückgabewert verwendet wird sehr schlechte praxisaber leider zwingen mich die Umstände dazu, also bitte ertrage es mit mir.

Betrachten Sie folgendes Beispiel:

#include <stdio.h>

void foo (int a) {
    printf("a: %d", a);
    break;
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
    }
    return 0;
}

Jetzt kompiliert dies nicht. Stattdessen erhalte ich einen Kompilierungsfehler wie folgt:

prog.c: In Funktion ‘foo’: prog.c:6:2: Fehler: Break-Anweisung nicht innerhalb der Schleife oder Schalter break;

Ich weiß, was das bedeutet (der Compiler sagt, dass der Einbruch foo() ist nicht innerhalb von a for Schleife)

Nun, was ich aus dem Standard bezüglich der Break-Anweisung finden konnte, ist Folgendes:

Die break-Anweisung bewirkt, dass die Steuerung an die Anweisung übergeben wird, die der innersten umschließenden while-, do-, for- oder switch-Anweisung folgt. Die Syntax ist einfach break;

Wenn man bedenkt, dass meine Funktion von innerhalb von a aufgerufen wird for Schleife, warum bricht die break-Anweisung nicht aus der for-Schleife aus? Ist es außerdem möglich, so etwas zu realisieren, ohne dass die Funktion zuerst zurückkehrt?

  • innerste umschließende while-, do-, for- oder switch-Anweisung Was es findet, ist die umschließende Funktion, also Fehler.

    – AchmadJP

    15. Februar 2016 um 8:08 Uhr


  • Suchen Sie nach “longjmp”. Oder, was ein besserer Ratschlag wäre, tun Sie es nicht.

    – Thomas Padron-McCarthy

    15. Februar 2016 um 8:09 Uhr


  • Was ist falsch am Putten? break direkt nach dem Anruf bei foo ?

    – Jabberwocky

    15. Februar 2016 um 8:16 Uhr


  • Verwenden longjmp() würde mit ziemlicher Sicherheit viel mehr Arbeit erfordern, als ein Rückgabeergebnis hinzuzufügen, das überprüft werden kann. Wenn Sie die Funktion ändern würden, um die hinzuzufügen breakwarum der Widerstand, es zu ändern, um ein Ergebnis zurückzugeben?

    – Michael Burr

    15. Februar 2016 um 8:19 Uhr

  • @Magisch die Anforderung, void zurückzugeben, klingt für mich wirklich seltsam. Sie sollten dies mit der Person besprechen, die für diese etwas fragwürdige Entscheidung verantwortlich ist.

    – Jabberwocky

    15. Februar 2016 um 8:24 Uhr

Benutzer-Avatar
chqrlie

Sie können nicht verwenden break; Auf diese Weise muss es im Körper von erscheinen for Schleife.

Es gibt mehrere Möglichkeiten, dies zu tun, aber keine davon wird empfohlen:

  • Sie können das Programm mit verlassen exit() Funktion. Da läuft die Schleife ab main() und Sie tun nichts danach, es ist möglich, auf diese Weise zu erreichen, was Sie wollen, aber es ist ein Sonderfall.

  • Sie können eine globale Variable in der Funktion setzen und diese in der testen for Schleife nach dem Funktionsaufruf. Die Verwendung globaler Variablen wird im Allgemeinen nicht empfohlen.

  • Sie können verwenden setjmp() und longjmp(), aber es ist, als würde man versuchen, eine Fliege mit einem Hammer zu zerquetschen, Sie könnten andere Dinge zerbrechen und die Fliege ganz verfehlen. Ich würde diese Vorgehensweise nicht empfehlen. Außerdem erfordert es a jmpbuf die Sie als globale Variable an die Funktion übergeben oder darauf zugreifen müssen.

Eine akzeptable Alternative ist die Übergabe der Adresse von a status Variable als zusätzliches Argument: Die Funktion kann sie so setzen, dass sie anzeigt, dass die Schleife unterbrochen werden muss.

Aber der bei weitem beste Ansatz in C ist die Rückgabe eines Werts, um die Fortsetzung zu testenes ist am lesbarsten.

Aus Ihren Erklärungen haben Sie nicht den Quellcode für foo() kann aber einige Bedingungen in einer Funktion erkennen, die Sie direkt oder indirekt aufrufen können foo(): longjmp() springt von seinem Standort, tief in das Innere von foo()möglicherweise viele Ebenen nach unten in der Aufrufliste, zum setjmp() Standort, Umgehung des regulären Funktions-Exit-Codes für alle Zwischenaufrufe. Wenn Sie genau das tun müssen, um einen Absturz zu vermeiden, setjmp() / longjmp() ist eine Lösung, aber es kann andere Probleme wie Ressourcenlecks, fehlende Initialisierung, inkonsistenten Status und andere Quellen für undefiniertes Verhalten verursachen.

Beachten Sie, dass Ihre for Schleife wird iteriert 101 da benutzt man mal die <= Operator. Das Idiomatische for Schleife verwendet for (int i = 0; i < 100; i++) um genau die Anzahl von Malen zu durchlaufen, die als obere (ausgeschlossene) Grenze erscheint.

  • Hervorzuheben ist, dass die Auswertung eines Rückgabewerts absolut der richtige Weg ist, da dies die beste Lesbarkeit ergibt.

    – rexkogitans

    15. Februar 2016 um 14:29 Uhr

Benutzer-Avatar
Thomas Padron-McCarthy

breakwie gotokann nur lokal innerhalb derselben Funktion springen, aber wenn es unbedingt sein muss, können Sie verwenden setjmp und longjmp:

#include <stdio.h>
#include <setjmp.h>

jmp_buf jump_target;

void foo(void)
{
    printf("Inside foo!\n");
    longjmp(jump_target, 1);
    printf("Still inside foo!\n");
}

int main(void) {
    if (setjmp(jump_target) == 0)
        foo();
    else
        printf("Jumped out!\n");
    return 0;
}

Der Aufruf an longjmp führt zu einem Rücksprung auf die setjmp Anruf. Der Rückgabewert von setjmp zeigt an, ob es nach dem Setzen des Sprungziels zurückkehrt oder ob es von einem Sprung zurückkehrt.

Ausgabe:

Inside foo!
Jumped out!

Nichtlokale Sprünge sind sicher, wenn sie richtig verwendet werden, aber es gibt eine Reihe von Dingen, über die Sie sorgfältig nachdenken sollten:

  • Seit longjmp springt “durch” alle Funktionsaktivierungen zwischen den setjmp anrufen und die longjmp aufrufen, wenn eine dieser Funktionen erwartet, dass sie nach dem aktuellen Ort der Ausführung zusätzliche Arbeit leisten kann, wird diese Arbeit einfach nicht erledigt.
  • Wenn die Funktion aktiviert wird, die aufgerufen wird setjmp beendet wurde, ist das Verhalten undefiniert. Alles kann passieren.
  • Wenn setjmp wurde also noch nicht angerufen jump_target ist nicht festgelegt, und das Verhalten ist undefiniert.
  • Lokale Variablen in der aufgerufenen Funktion setjmp kann unter Umständen undefinierte Werte annehmen.
  • Ein Wort: Fäden.
  • Andere Dinge, wie z. B. dass Gleitkomma-Statusflags möglicherweise nicht beibehalten werden und dass es Einschränkungen gibt, wo Sie die platzieren können setjmp Anruf.

Die meisten davon folgen natürlich, wenn Sie ein gutes Verständnis dafür haben, was ein nicht lokaler Sprung auf der Ebene von Maschinenanweisungen und CPU-Registern bewirkt, aber wenn Sie das nicht haben, und gelesen habe, was der C-Standard tut und was nicht, würde ich zu etwas Vorsicht raten.

  • @Magisch: Bin gespannt wie setjmp/longjmp würde das erforderliche “Umschreiben” im Vergleich zum Zurückgeben eines zu überprüfenden Wertes reduzieren? Verwenden setjmp/longjmp erfordert, dass sowohl die Aufrufsite als auch die Funktion geändert werden. Das Hinzufügen eines Rückgabewerts ist in vielen Fällen gleich – außer dass nur Sites aufgerufen werden, die in der Lage sein müssen, das auszuführen break geändert werden müssen. Andere Aufrufseiten können die Funktion einfach weiter aufrufen und den Rückgabewert ignorieren.

    – Michael Burr

    15. Februar 2016 um 8:44 Uhr

  • @Magisch Ich verstehe immer noch nicht, warum Sie diesen schlecht geschriebenen Teil nicht ändern können, um etwas anderes als void zurückzugeben, aber Sie kann Ändern Sie es, indem Sie den setjmp/longjmp-Fuzz einführen.

    – Jabberwocky

    15. Februar 2016 um 8:59 Uhr

  • @Magisch: Passen Sie auf, dass Sie möglicherweise immer noch den Code brechen, obwohl Sie die explizite Anforderung für eine void-Rückgabe umgangen haben. Wenn Ihre Funktion „void zurückgeben muss“, dann könnte der Code Ihres ehemaligen Kollegen insbesondere in der Annahme geschrieben worden sein, dass er überhaupt „zurückgeben muss“. Aber Sie haben jetzt eine Funktion geschrieben, die nicht zurückkehrt, sondern stattdessen herausspringt. Überprüfen Sie sehr sorgfältig, dass Ihr Kollege keinen Bereinigungscode geschrieben hat, an dem Sie jetzt vorbeispringen, denn wenn dies der Fall ist, führt das Überspringen wahrscheinlich zu Ressourcenlecks oder hinterlässt einige Datenstrukturen in einem inkonsistenten Zustand.

    – Steve Jessop

    15. Februar 2016 um 10:53 Uhr


  • @Magisch: Besonders nachdem ich Ihren letzten Kommentar gelesen habe, möchte ich etwas vorsichtig sein, wenn ich in diesem Programm mit Weitsprüngen herumstampfe. Aber wenn es der einzige Weg ist und sorgfältige Tests keine Probleme finden, dann können Sie vielleicht die Daumen drücken und auf das Beste hoffen. Sagen Sie mir einfach, in welchen Flugzeugtyp das eingebaut wird, damit ich weiß, welche Flüge ich vermeiden muss…

    – Thomas Padron-McCarthy

    15. Februar 2016 um 10:53 Uhr


  • Es könnte sich lohnen, zusätzlich zu dieser Antwort eine große Warnung “Das ist normalerweise eine sehr schlechte Idee” hinzuzufügen.

    – Ixrec

    15. Februar 2016 um 13:20 Uhr

Benutzer-Avatar
Jack Aidley

(Hinweis: Die Frage wurde bearbeitet, seit ich sie ursprünglich geschrieben habe.)

Aufgrund der Art und Weise, wie C kompiliert ist, muss es wissen, wohin es wechseln soll, wenn die Funktion aufgerufen wird. Da man es von überall anrufen kann, oder sogar irgendwo eine Pause keinen Sinn macht, kann man das nicht haben break; Anweisung in Ihrer Funktion und lassen Sie es so funktionieren.

Andere Antworten haben schreckliche Lösungen vorgeschlagen, z. B. das Festlegen einer globalen Variablen mit a #define oder Weitsprung(!) aus der Funktion heraus. Das sind extrem schlechte Lösungen. Stattdessen sollten Sie die Lösung verwenden, die Sie in Ihrem einleitenden Absatz fälschlicherweise verwerfen, und einen Wert von Ihrer Funktion zurückgeben, der den Zustand angibt, den Sie auslösen möchten break in diesem Fall und mach so etwas:

#include <stdbool.h>

bool checkAndDisplay(int n)
{
    printf("%d\n", n);
    return (n == 14);
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        if (checkAndDisplay(i))
            break;
    }
    return 0;
}

Der Versuch, obskure Wege zu finden, um solche Dinge zu erreichen, anstatt den richtigen Weg zu verwenden, um das gleiche Endergebnis zu erzielen, ist ein todsicherer Weg, um Code von schlechter Qualität zu generieren, dessen Wartung und Debugging ein Albtraum ist.

Sie erwähnen, versteckt in einem Kommentar, dass Sie eine void-Rückgabe verwenden müssen, dies ist kein Problem, übergeben Sie einfach den Break-Parameter als Zeiger:

#include <stdbool.h>

void checkAndDisplay(int n, bool* wantBreak)
{
    printf("%d\n", n);
    if (n == 14)
        wantBreak = true;
}

int main(void) {
    bool wantBreak = false;
    for (int i = 0; i <= 100; i++) {
        checkAndDisplay(i, &wantBreak);
        if (wantBreak)
            break;
    }
    return 0;
}

Da Ihre Parameter fester Typ sind, schlage ich vor, dass Sie einen Cast verwenden, um den Zeiger auf einen der Parameter zu übergeben, z foo(a, b, (long)&out);

  • Es ist hilfreich, wenn Sie die Einschränkung in Ihre Frage aufnehmen, anstatt sie in einem Kommentar zu verstecken. Ich habe einen Abschnitt über eine ungültige Rückgabe hinzugefügt.

    – Jack Aidley

    15. Februar 2016 um 14:15 Uhr

  • Ich erörtere ausdrücklich, dass ich in meiner Frage eine Alternative zur Verwendung von Rückgabewerten haben möchte. Reicht das nicht aus? Wenn Sie für meinen Anwendungsfall hyperspezifisch werden möchten (imo nicht hilfreich für zukünftige Besucher, aber ok), wird die Funktion von der Bibliothek vorgeschrieben, um das Format zu haben void foo (int passV, int aVal, long bVal) Das Dokument für die Bibliothek erwähnt dies ausdrücklich, und wenn Sie es nicht verwenden, stürzt das Programm mit einem Segfault ab. Ich habe es versucht.

    – Magisch

    15. Februar 2016 um 14:16 Uhr


  • Nein, weil Sie nicht sagen, dass es eine Voraussetzung ist; es klingt nur so, als wärst du ignorant.

    – Jack Aidley

    15. Februar 2016 um 14:17 Uhr

  • Die Datentypen sind vorgeschrieben. Schlagen Sie vor, dass ich einen meiner Parameter missbrauche, um stattdessen einen Zeiger zu übergeben? Ich kann das vielleicht tun, wenn ich zwei meiner Parameter mit ein wenig Bitshifting in einen verarbeite und sie dann innerhalb der Funktion entwirre.

    – Magisch

    15. Februar 2016 um 14:33 Uhr

  • Ich denke immer noch, dass eine statische globale Variable viel einfacher sein wird als Zeiger zu verwirren …

    – Falko

    15. Februar 2016 um 14:40 Uhr

Benutzer-Avatar
Zbynek Vyskovsky – kvr000

break ist eine Anweisung, die während der Kompilierzeit aufgelöst wird. Daher muss der Compiler eine geeignete for/while-Schleife innerhalb derselben Funktion finden. Beachten Sie, dass es keine Garantie dafür gibt, dass die Funktion nicht von woanders aufgerufen werden kann.

Benutzer-Avatar
Frodo

Wenn Sie die nicht verwenden können break Anweisung könnten Sie eine lokale Variable in Ihrem Modul definieren und eine zweite Laufbedingung hinzufügen for Schleife. Zum Beispiel wie der folgende Code:

#include <stdio.h>
#include <stdbool.h>

static bool continueLoop = true;

void foo (int a)
{
    bool doBreak = true;

    printf("a: %d",a);

    if(doBreak == true){
        continueLoop = false;
    }
    else {
        continueLoop = true;
    }
}
int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; (i <= 100) && continueLoop; i++)
    {
        foo(i);
    }
    return 0;
}

Beachten Sie, dass dies in diesem Beispiel nicht genau a ist break-Anweisung, aber die forSchleife führt keine weitere Iteration durch. Wenn Sie eine tun möchten break du musst ein einfügen if-Bedingung mit der Variablen continueLoop was dazu führt break:

int main(void) {
    continueLoop = true;   // Has to be true before entering the loop
    for (int i = 0; i <= 100; i++)
    {
        foo(i);
        if(!continueLoop){
            break;
        }
    }
    return 0;
}

  • Das ist natürlich die richtige Antwort. Die Frage “Wie beendet man eine for-Schleife?” for (;CONDITION;) ist wie die Frage “Wie beendet man eine While-Schleife?” while(CONDITION). Beachten Sie den großen fetten ZUSTAND 🙂

    – Fett

    15. Februar 2016 um 23:53 Uhr


Benutzer-Avatar
wizzwizz4

Ich glaube, es hängt damit zusammen, wie a break Anweisung wird in Maschinencode übersetzt. Das break -Anweisung wird als unbedingte Verzweigung zum Label unmittelbar nach der Schleife oder dem Schalter übersetzt.

mov ECX,5
label1:
  jmp <to next instruction address>  ;break
loop label1
<next instruction>

Während der Anruf zu foo() von innerhalb der Schleife führt zu etwas wie

mov ECX,5
label1:
  call <foo address>
loop label1
<next instruction>

und bei foo die Anschrift

call <printf address>
jmp <to where?> ;break cannot translate to any address.

  • Das ist natürlich die richtige Antwort. Die Frage “Wie beendet man eine for-Schleife?” for (;CONDITION;) ist wie die Frage “Wie beendet man eine While-Schleife?” while(CONDITION). Beachten Sie den großen fetten ZUSTAND 🙂

    – Fett

    15. Februar 2016 um 23:53 Uhr


Benutzer-Avatar
Remco Gerlich

Dies ist eine weitere Idee, die machbar sein kann oder auch nicht: Behalten Sie eine Variable bei, die foo in eine No-Op verwandeln kann:

int broken = 0;

void foo (int a) {
    if (broken) return;

    printf("a: %d", a);
    broken = 1; // "break"
}

int main(void) {
    for (int i = 0; i <= 100; i++) {
        foo(i);
    }
    return 0;
}

Dies ist funktionell dasselbe, abgesehen von einigen Taktzyklenverlusten (die Funktion wird aufgerufen, führt aber nur die if -Anweisung), und es besteht keine Notwendigkeit, die Schleife zu ändern. Es ist nicht threadsicher und funktioniert nur beim ersten Mal (aber foo könnte die zurücksetzen broken Variable auf 0, wenn mit aufgerufen a gleich 0, falls erforderlich).

Also nicht toll, aber eine Idee, die noch nicht erwähnt wurde.

  • wenn entsprechend können Sie es einfach in den Zustand des for setzen. for(blah; i<101 && whatever(); ++i)

    – Fett

    15. Februar 2016 um 23:55 Uhr

  • Ich nahm an, dass die for-Schleife im Code nicht unter seiner Kontrolle war, nur so machte sein Problem für mich Sinn.

    – Remco Gerlich

    16. Februar 2016 um 7:41 Uhr

1381830cookie-checkAusbrechen aus einer Schleife innerhalb einer Funktion, die in dieser Schleife aufgerufen wird

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

Privacy policy