Probieren Sie Catch-Anweisungen in C aus

Lesezeit: 12 Minuten

Ich habe heute über die Try/Catch-Blöcke nachgedacht, die es in anderen Sprachen gibt. Habe das eine Weile gegoogelt aber ohne Ergebnis. Soweit ich weiß, gibt es in C so etwas wie Try/Catch nicht. Gibt es jedoch eine Möglichkeit, sie zu “simulieren”?
Sicher, es gibt Assert und andere Tricks, aber nichts wie Try/Catch, die auch die ausgelöste Ausnahme abfangen. Vielen Dank

  • Ausnahmeähnliche Mechanismen werden ohne einen Mechanismus zum automatischen Freigeben von Ressourcen beim Entladen des Stapels nicht allgemein nützlich sein. C++ verwendet RAII; Java, C#, Python usw. verwenden Garbage Collectors. (Und beachten Sie, dass Garbage Collectors nur Speicher freigeben. Um andere Arten von Ressourcen automatisch freizugeben, fügen sie auch Dinge wie Finalizer oder Kontextmanager hinzu …)

    – jamesdlin

    3. Mai 2015 um 5:34 Uhr

  • @jamesdlin, warum konnten wir RAII nicht mit C machen?

    – Schrittmacher

    15. Mai 2015 um 22:45 Uhr

  • @Pacerier RAII erfordert den automatischen Aufruf von Funktionen, wenn Objekte zerstört werden (dh Destruktoren). Wie schlagen Sie vor, das in C zu tun?

    – jamesdlin

    15. Mai 2015 um 23:02 Uhr

Benutzeravatar von JaredPar
JaredPar

C selbst unterstützt keine Ausnahmen, aber Sie können sie bis zu einem gewissen Grad mit simulieren setjmp und longjmp Anrufe.

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened here\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjmp(s_jumpBuffer, 42);
}

Diese Website hat ein nettes Tutorial, wie man Ausnahmen mit simuliert setjmp und longjmp

  • geniale lösung! ist diese Lösung Kreuz? Es hat bei mir auf MSVC2012 funktioniert, aber nicht im MacOSX Clang-Compiler.

    – Mannysz

    5. September 2016 um 18:03 Uhr

  • Hinweis: Ich dachte, dass Sie mit try-catch-Klauseln Ausnahmen abfangen können (wie das Teilen durch Null). Diese Funktion scheint Ihnen nur zu erlauben, Ausnahmen abzufangen, die Sie selbst auslösen. Echte Ausnahmen werden nicht ausgelöst, wenn longjmp aufgerufen wird, oder? Wenn ich diesen Code verwende, um so etwas zu tun try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; es wird nicht richtig funktionieren?

    – Sam

    13. Februar 2019 um 15:52 Uhr

  • Dividieren durch Null ist in C++ nicht einmal eine Ausnahme. Um damit umzugehen, müssen Sie entweder überprüfen, ob der Divisor nicht Null ist, und ihn behandeln, oder das SIGFPE behandeln, das ausgelöst wird, wenn Sie eine Formel zum Devidieren durch Null ausführen.

    – James

    27. März 2019 um 13:15 Uhr

  • Ich komme aus der Java-Welt, wo Sie eine allgemeine Ausnahme abfangen können. Ich habe die gleichen Bedenken wie @Sam. Was ist mit der Dereferenzierung von Nullzeigern? Oder eine Funktion aufrufen, die intern fehlschlägt?

    – Zeitgeist

    18. März 2021 um 19:34 Uhr

  • @Heroman C hat keine Ausnahmen überhaupt. C++ hat Ausnahmen, aber von deren Verwendung wird im Allgemeinen abgeraten; und im Gegensatz zu Java und .NET gibt es keinen Sprachstandard-Ausnahmebasistyp (dh Sie können alles werfen, nicht nur Unterklassen von std:exception). In C++ Dereferenzierung a nullptr wirft nichts, stattdessen ist es UB, was bedeutet, dass Sie sehen müssen, ob Ihr Compiler oder Betriebssystem Zugriffsverletzungen ordnungsgemäß behandelt (die meisten tun dies, aber es ist nicht im ISO-Standard C++).

    – Dai

    16. November 2021 um 1:21 Uhr


Sie nutzen gehe zu in C für ähnliche Fehlerbehandlungssituationen.
Das ist das nächste Äquivalent von Ausnahmen, die Sie in C bekommen können.

  • @JensGustedt Dies ist genau das, wofür goto derzeit sehr oft verwendet wird und ein Beispiel, wo es sinnvoll ist (setjmp/ljmp ist eine bessere Alternative, aber label+goto ist typisch mehr gebraucht).

    – Tomás Pruzina

    9. Februar 2013 um 12:26 Uhr

  • @AoeAoe, wahrscheinlich goto dient eher der Fehlerbehandlung, aber was solls? Die Frage bezieht sich nicht auf die Fehlerbehandlung als solche, sondern explizit auf Try/Catch-Äquivalente. goto ist kein Äquivalent zu try/catch, da es auf dieselbe Funktion beschränkt ist.

    – Jens Gustedt

    9. Februar 2013 um 14:40 Uhr


  • @JensGustedt Ich habe irgendwie auf Hass / Angst vor goto und Leuten reagiert, die es benutzen (meine Lehrer haben mir auch gruselige Geschichten über die Verwendung von goto an der Universität erzählt). [OT] Das einzige, was wirklich, wirklich riskant und “wolkig” an goto ist, ist “goto rückwärts”, aber ich habe das in Linux VFS gesehen (Git-Schuldiger schwor, dass es leistungskritisch und vorteilhaft war).

    – Tomás Pruzina

    12. Februar 2013 um 18:56 Uhr


  • Sehen systemctl-Quellen für legitime Verwendungen von goto als Try/Catch-Mechanismus, der in einer modernen, weithin akzeptierten, peer-reviewten Quelle verwendet wird. Suche goto für ein “Wurf”-Äquivalent und finish für ein “Fang”-Äquivalent.

    – Steward

    28. Februar 2019 um 13:36 Uhr

  • Diese Antwort scheint die beste und portabelste zu sein, sehen Sie, wie php/bcmath sie verwendet: github.com/php/php-src/blob/php-8.0.7/ext/bcmath/bcmath.c#L455

    – mvorisek

    25. Juni 2021 um 14:39 Uhr

Benutzeravatar von Paul Hutchinson
Paul Hutchinson

Ok, ich konnte nicht widerstehen darauf zu antworten. Lassen Sie mich zuerst sagen, dass ich es nicht für eine gute Idee halte, dies in C zu simulieren, da es wirklich ein Fremdkonzept für C ist.

Wir können verwenden den Präprozessor und die lokalen Stack-Variablen missbrauchen, um eine eingeschränkte Version von C++ try/throw/catch zu verwenden.

Version 1 (Auslösungen im lokalen Bereich)

#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

Version 1 ist nur ein lokaler Wurf (kann den Gültigkeitsbereich der Funktion nicht verlassen). Es stützt sich auf die Fähigkeit von C99, Variablen im Code zu deklarieren (es sollte in C89 funktionieren, wenn try das erste in der Funktion ist).

Diese Funktion erstellt einfach eine lokale Variable, damit sie weiß, ob ein Fehler aufgetreten ist, und verwendet ein goto, um zum Catch-Block zu springen.

Zum Beispiel:

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

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) {__HadError=true;goto ExitJmp;}

int main(void)
{
    try
    {
        printf("One\n");
        throw();
        printf("Two\n");
    }
    catch(...)
    {
        printf("Error\n");
    }
    return 0;
}

Das funktioniert zu etwas wie:

int main(void)
{
    bool HadError=false;
    {
        printf("One\n");
        {
            HadError=true;
            goto ExitJmp;
        }
        printf("Two\n");
    }
ExitJmp:
    if(HadError)
    {
        printf("Error\n");
    }
    return 0;
}

Version 2 (Bereichsspringen)

#include <stdbool.h>
#include <setjmp.h>

jmp_buf *g__ActiveBuf;

#define try jmp_buf __LocalJmpBuff;jmp_buf *__OldActiveBuf=g__ActiveBuf;bool __WasThrown=false;g__ActiveBuf=&__LocalJmpBuff;if(setjmp(__LocalJmpBuff)){__WasThrown=true;}else
#define catch(x) g__ActiveBuf=__OldActiveBuf;if(__WasThrown)
#define throw(x) longjmp(*g__ActiveBuf,1);

Version 2 ist viel komplexer, funktioniert aber im Grunde genauso. Es verwendet einen langen Sprung aus der aktuellen Funktion zum Try-Block. Der try-Block verwendet dann ein if/else, um den Codeblock zum catch-Block zu überspringen, der die lokale Variable überprüft, um zu sehen, ob sie fangen soll.

Das Beispiel noch einmal erweitert:

jmp_buf *g_ActiveBuf;

int main(void)
{
    jmp_buf LocalJmpBuff;
    jmp_buf *OldActiveBuf=g_ActiveBuf;
    bool WasThrown=false;
    g_ActiveBuf=&LocalJmpBuff;

    if(setjmp(LocalJmpBuff))
    {
        WasThrown=true;
    }
    else
    {
        printf("One\n");
        longjmp(*g_ActiveBuf,1);
        printf("Two\n");
    }
    g_ActiveBuf=OldActiveBuf;
    if(WasThrown)
    {
        printf("Error\n");
    }
    return 0;
}

Dies verwendet einen globalen Zeiger, damit longjmp() weiß, welcher Versuch zuletzt ausgeführt wurde. Wir sind verwenden Missbrauch des Stapels, sodass untergeordnete Funktionen auch einen Try/Catch-Block haben können.

Die Verwendung dieses Codes hat eine Reihe von Nachteilen (ist aber eine lustige mentale Übung):

  • Es wird keinen zugewiesenen Speicher freigeben, da keine Dekonstruktoren aufgerufen werden.
  • Sie können nicht mehr als 1 Try/Catch in einem Bereich haben (keine Verschachtelung)
  • Sie können keine Ausnahmen oder andere Daten wie in C++ auslösen
  • Überhaupt nicht threadsicher
  • Sie richten andere Programmierer zum Scheitern ein, weil sie den Hack wahrscheinlich nicht bemerken und versuchen werden, sie wie C++ Try/Catch-Blöcke zu verwenden.

  • nette Alternativlösungen.

    – Haseeb Mir

    17. März 2018 um 5:35 Uhr

  • Version 1 ist eine nette Idee, aber diese __HadError-Variable müsste zurückgesetzt oder eingeschränkt werden. Andernfalls können Sie nicht mehr als einen Try-Catch im selben Block verwenden. Verwenden Sie vielleicht eine globale Funktion wie bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. Aber die lokale Variable würde auch neu definiert werden, so dass die Dinge ein wenig außer Kontrolle geraten.

    – Flammenwelle000

    11. März 2020 um 20:06 Uhr

  • Ja, es ist auf einen Try-Catch in derselben Funktion beschränkt. Ein größeres Problem als die Variable ist jedoch das Label, da Sie keine doppelten Labels in derselben Funktion haben können.

    – Paul Hutchinson

    23. März 2020 um 15:25 Uhr

  • Eigentlich habe ich das gleiche Problem, dass ich t2o nicht verwenden kann oder mehr als versuchen, in einem Bereich zu fangen. Haben Sie eine Lösung dafür?

    – Parham sagharichi ha

    18. September 2021 um 6:34 Uhr

  • @Parhamsagharichiha dafür gibt es keine einfache Lösung. Ich würde vorschlagen, dass Sie einfach das goto direkt verwenden, anstatt zu versuchen, ein try..catch zu verwenden.

    – Paul Hutchinson

    23. September 2021 um 21:05 Uhr

Benutzeravatar von Kerrek SB
Kerrek SB

In C99, Sie können verwenden setjmp/longjmp für nicht-lokalen Kontrollfluss.

Innerhalb eines einzigen Gültigkeitsbereichs wird das generische, strukturierte Codierungsmuster für C bei Vorhandensein mehrerer Ressourcenzuordnungen und mehrerer Exits verwendet goto, wie in diesem Beispiel. Dies ähnelt der Art und Weise, wie C++ Destruktoraufrufe von automatischen Objekten unter der Haube implementiert, und wenn Sie sich sorgfältig daran halten, sollte es Ihnen selbst bei komplexen Funktionen ein gewisses Maß an Sauberkeit ermöglichen.

Benutzeravatar von Michael Anderson
Michael Anderson

Während einige der anderen Antworten die einfachen Fälle mit behandelt haben setjmp und longjmpin einer echten Anwendung gibt es zwei Dinge, die wirklich wichtig sind.

  1. Verschachtelung von Try/Catch-Blöcken. Verwenden einer einzigen globalen Variablen für Ihre jmp_buf wird diese nicht funktionieren.
  2. Einfädeln. Eine einzige globale Variable für Sie jmp_buf wird in dieser Situation alle Arten von Schmerzen verursachen.

Die Lösung für diese Probleme besteht darin, einen Thread-lokalen Stapel von jmp_buf die aktualisiert werden, während Sie gehen. (Ich denke, das ist es, was Lua intern verwendet).

Also stattdessen (aus JaredPars großartiger Antwort)

static jmp_buf s_jumpBuffer;

void Example() { 
  if (setjmp(s_jumpBuffer)) {
    // The longjmp was executed and returned control here
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
  }
}

void Test() {
  // Rough equivalent of `throw`
  longjump(s_jumpBuffer, 42);
}

Sie würden so etwas verwenden wie:

#define MAX_EXCEPTION_DEPTH 10;
struct exception_state {
  jmp_buf s_jumpBuffer[MAX_EXCEPTION_DEPTH];
  int current_depth;
};

int try_point(struct exception_state * state) {
  if(current_depth==MAX_EXCEPTION_DEPTH) {
     abort();
  }
  int ok = setjmp(state->jumpBuffer[state->current_depth]);
  if(ok) {
    state->current_depth++;
  } else {
    //We've had an exception update the stack.
    state->current_depth--;
  }
  return ok;
}

void throw_exception(struct exception_state * state) {
  longjump(state->current_depth-1,1);
}

void catch_point(struct exception_state * state) {
    state->current_depth--;
}

void end_try_point(struct exception_state * state) {
    state->current_depth--;
}

__thread struct exception_state g_exception_state; 

void Example() { 
  if (try_point(&g_exception_state)) {
    catch_point(&g_exception_state);
    printf("Exception happened\n");
  } else {
    // Normal code execution starts here
    Test();
    end_try_point(&g_exception_state);
  }
}

void Test() {
  // Rough equivalent of `throw`
  throw_exception(g_exception_state);
}

Wiederum würde eine realistischere Version davon eine Möglichkeit beinhalten, Fehlerinformationen in der zu speichern exception_statebessere Handhabung von MAX_EXCEPTION_DEPTH (Vielleicht mit realloc, um den Puffer zu vergrößern, oder so ähnlich).

HAFTUNGSAUSSCHLUSS: Der obige Code wurde ohne jegliche Tests geschrieben. Es dient nur dazu, eine Vorstellung davon zu bekommen, wie man Dinge strukturiert. Unterschiedliche Systeme und unterschiedliche Compiler müssen den Thread-Lokalspeicher unterschiedlich implementieren. Der Code enthält wahrscheinlich sowohl Kompilierfehler als auch Logikfehler – Sie können ihn also nach Belieben verwenden, aber TESTEN Sie ihn, bevor Sie ihn verwenden 😉

Benutzeravatar von keebus
keebus

Dies ist eine weitere Möglichkeit zur Fehlerbehandlung in C, die performanter ist als die Verwendung von setjmp/longjmp. Leider funktioniert es nicht mit MSVC, aber wenn nur GCC/Clang verwendet werden kann, sollten Sie es in Betracht ziehen. Insbesondere verwendet es die Erweiterung “Label als Wert”, mit der Sie die Adresse eines Labels nehmen, in einem Wert speichern und bedingungslos dorthin springen können. Ich stelle es anhand eines Beispiels vor:

GameEngine *CreateGameEngine(GameEngineParams const *params)
{
    /* Declare an error handler variable. This will hold the address
       to jump to if an error occurs to cleanup pending resources.
       Initialize it to the err label which simply returns an
       error value (NULL in this example). The && operator resolves to
       the address of the label err */
    void *eh = &&err;

    /* Try the allocation */
    GameEngine *engine = malloc(sizeof *engine);
    if (!engine)
        goto *eh; /* this is essentially your "throw" */

    /* Now make sure that if we throw from this point on, the memory
       gets deallocated. As a convention you could name the label "undo_"
       followed by the operation to rollback. */
    eh = &&undo_malloc;

    /* Now carry on with the initialization. */
    engine->window = OpenWindow(...);
    if (!engine->window)
        goto *eh;   /* The neat trick about using approach is that you don't
                       need to remember what "undo" label to go to in code.
                       Simply go to *eh. */

    eh = &&undo_window_open;

    /* etc */

    /* Everything went well, just return the device. */
    return device;

    /* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}

Wenn Sie möchten, können Sie gemeinsamen Code in Defines umgestalten und so effektiv Ihr eigenes Fehlerbehandlungssystem implementieren.

/* Put at the beginning of a function that may fail. */
#define declthrows void *_eh = &&err

/* Cleans up resources and returns error result. */
#define throw goto *_eh

/* Sets a new undo checkpoint. */
#define undo(label) _eh = &&undo_##label

/* Throws if [condition] evaluates to false. */
#define check(condition) if (!(condition)) throw

/* Throws if [condition] evaluates to false. Then sets a new undo checkpoint. */
#define checkpoint(label, condition) { check(condition); undo(label); }

Dann wird das Beispiel

GameEngine *CreateGameEngine(GameEngineParams const *params)
{
    declthrows;

    /* Try the allocation */
    GameEngine *engine = malloc(sizeof *engine);
    checkpoint(malloc, engine);

    /* Now carry on with the initialization. */
    engine->window = OpenWindow(...);
    checkpoint(window_open, engine->window);

    /* etc */

    /* Everything went well, just return the device. */
    return device;

    /* After the return, insert your cleanup code in reverse order. */
undo_window_open: CloseWindow(engine->window);
undo_malloc: free(engine);
err: return NULL;
}

Benutzeravatar von James Adam
James Adam

Eine schnelle Google-Suche ergibt kludgey Lösungen wie Dies die setjmp/longjmp verwenden, wie andere erwähnt haben. Nichts ist so einfach und elegant wie C++/Java’s try/catch. Ich bin ziemlich parteiisch gegenüber Adas Ausnahmebehandlung.

Überprüfen Sie alles mit if-Anweisungen 🙂

1423340cookie-checkProbieren Sie Catch-Anweisungen in C aus

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

Privacy policy