Versuchen Sie catch-Anweisungen in C.

101

Ich habe heute über die Try / Catch-Blöcke nachgedacht, die in anderen Sprachen existieren. Googelte eine Weile, aber ohne Ergebnis. Soweit ich weiß, gibt es in C kein Try / Catch. Gibt es jedoch eine Möglichkeit, sie zu "simulieren"?
Klar, es gibt Assert und andere Tricks, aber nichts wie try / catch, die auch die ausgelöste Ausnahme abfangen. Danke dir

Andrew
quelle
3
Ausnahmeähnliche Mechanismen sind ohne einen Mechanismus zum automatischen Freigeben von Ressourcen beim Abwickeln des Stapels im Allgemeinen nicht nützlich. C ++ verwendet RAII; Java, C #, Python usw. verwenden Garbage Collectors. (Und beachten Sie, dass Garbage Collectors nur Speicher
freigeben
@ Jamesdlin, warum konnten wir RAII nicht mit C machen?
Pacerier
1
@Pacerier RAII erfordert das automatische Aufrufen von Funktionen, wenn Objekte zerstört werden (dh Destruktoren). Wie schlagen Sie das in C vor?
Jamesdlin

Antworten:

90

C selbst unterstützt keine Ausnahmen, aber Sie können sie bis zu einem gewissen Grad mit setjmpund longjmpaufrufen.

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 enthält ein nettes Tutorial zum Simulieren von Ausnahmen mit setjmpundlongjmp

JaredPar
quelle
1
tolle Lösung! Ist diese Lösung Kreuz? Es funktionierte für mich unter MSVC2012, aber nicht im MacOSX Clang-Compiler.
Mannysz
1
Hinweis: Ich dachte, mit try catch-Klauseln können Sie Ausnahmen abfangen (z. B. durch Null teilen). Diese Funktion scheint es Ihnen nur zu ermöglichen, Ausnahmen zu fangen, die Sie selbst auslösen. Echte Ausnahmen werden nicht durch den Aufruf von longjmp ausgelöst, oder? Wenn ich diesen Code verwende, um so etwas zu tun, funktioniert try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; es nicht richtig?
Sam
Devide by Zero ist in C ++ nicht einmal eine Ausnahme. Um damit umzugehen, müssen Sie entweder überprüfen, ob der Divisor nicht Null ist, und damit umgehen oder das SIGFPE behandeln, das ausgelöst wird, wenn Sie eine Devide by Zero-Formel ausführen.
James
24

Sie verwenden goto in C für ähnliche Fehlerbehandlungssituationen.
Dies ist das nächste Äquivalent zu Ausnahmen, die Sie in C erhalten können.

Alok Speichern
quelle
3
@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 wird normalerweise häufiger verwendet).
Tomas Pruzina
1
@AoeAoe, wird wahrscheinlich gotoeher zur Fehlerbehandlung verwendet, aber was nun? Die Frage bezieht sich nicht auf die Fehlerbehandlung als solche, sondern explizit auf Try / Catch-Äquivalente. gotoist kein Äquivalent für try / catch, da es auf dieselbe Funktion beschränkt ist.
Jens Gustedt
@JensGustedt Ich reagierte irgendwie auf Hass / Angst vor Goto und Menschen, die es benutzen (meine Lehrer erzählten mir Gruselgeschichten über Goto-Nutzung auch an der Universität). [OT] Das einzige, was wirklich, wirklich riskant und "trübe" an goto ist, ist "rückwärts gehen", aber ich habe das in Linux VFS gesehen (Git-Schuld-Typ schwor, dass es leistungskritisch-vorteilhaft war).
Tomas Pruzina
Siehe systemctl-Quellen für legitime Verwendungen gotoals Try / Catch-Mechanismus, der in einer modernen, allgemein akzeptierten, von Experten geprüften Quelle verwendet wird. Suchen Sie gotonach einem "Wurf" -Äquivalent und finishnach einem "Fang" -Äquivalent.
Stewart
12

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

Wir können verwenden die Prä - Prozessor und lokalen Stack - Variablen missbrauchen Verwendung eine eingeschränkte Version von C ++ try / throw / catch zu geben.

Version 1 (lokaler Gültigkeitsbereich wirft)

#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 Funktionsumfang nicht verlassen). Es hängt von der Fähigkeit von C99 ab, Variablen im Code zu deklarieren (es sollte in C89 funktionieren, wenn der Versuch als erstes in der Funktion ist).

Diese Funktion erstellt nur eine lokale Variable, damit sie weiß, ob ein Fehler aufgetreten ist, und springt mit einem goto zum catch-Block.

Beispielsweise:

#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 wie folgt:

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 (Scope Jumping)

#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 grundsätzlich genauso. Es wird ein langer Sprung von der aktuellen Funktion zum try-Block verwendet. Der try-Block verwendet dann ein if / else, um den Codeblock zum catch-Block zu überspringen, der die lokale Variable überprüft, um festzustellen, ob sie abfangen soll.

Das Beispiel wurde erneut 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 verwenden den Stapel, damit 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):

  • Der zugewiesene Speicher wird nicht freigegeben, da keine Dekonstruktoren aufgerufen werden.
  • Sie können nicht mehr als 1 Versuch / Fang 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 für einen Fehler ein, da sie den Hack wahrscheinlich nicht bemerken und versuchen, sie wie C ++ - Try / Catch-Blöcke zu verwenden.
Paul Hutchinson
quelle
schöne alternative lösungen.
HaseeB Mir
Version 1 ist eine gute Idee, aber diese __HadError-Variable müsste zurückgesetzt oder mit einem Gültigkeitsbereich versehen werden. Andernfalls können Sie nicht mehr als einen Try-Catch im selben Block verwenden. Verwenden Sie möglicherweise eine globale Funktion wie bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. Die lokale Variable würde aber auch neu definiert, sodass die Dinge etwas außer Kontrolle geraten.
flamewave000
Ja, es ist auf einen Try-Catch in derselben Funktion beschränkt. Ein größeres Problem als die Variable ist jedoch die Beschriftung, da Sie keine doppelten Beschriftungen in derselben Funktion haben können.
Paul Hutchinson
10

In C99 können Sie setjmp/ longjmpfür den nicht lokalen Kontrollfluss verwenden.

Innerhalb eines einzelnen Bereichs wird das generische, strukturierte Codierungsmuster für C bei Vorhandensein mehrerer Ressourcenzuweisungen und mehrerer Exits gotowie in diesem Beispiel verwendet . Dies ähnelt der Implementierung von Destruktoraufrufen von automatischen Objekten unter der Haube. Wenn Sie sich sorgfältig daran halten, sollte dies auch bei komplexen Funktionen ein gewisses Maß an Sauberkeit ermöglichen.

Kerrek SB
quelle
5

Während einige der anderen Antworten die einfachen Fälle mit setjmpund behandelt habenlongjmp , gibt es in einer realen Anwendung zwei Bedenken, die wirklich wichtig sind.

  1. Verschachtelung von Try / Catch-Blöcken. Verwenden einer einzelnen globalen Variablen für Ihrejmp_buf , funktionieren diese nicht.
  2. Einfädeln. Eine einzelne globale Variable für Siejmp_buf verursacht in dieser Situation alle Arten von Schmerzen.

Die Lösung für diese besteht darin, einen threadlokalen Stapel von zu verwalten jmp_buf , der im Laufe der Zeit aktualisiert wird. (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 wie verwenden:

#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_state , besseren Handhabung vonMAX_EXCEPTION_DEPTH (möglicherweise Realloc verwenden, um den Puffer zu vergrößern, oder so ähnlich).

HAFTUNGSAUSSCHLUSS: Der obige Code wurde ohne jegliche Prüfung geschrieben. Es ist nur so, dass Sie eine Vorstellung davon bekommen, wie man Dinge strukturiert. Unterschiedliche Systeme und unterschiedliche Compiler müssen den lokalen Thread-Speicher unterschiedlich implementieren. Der Code enthält wahrscheinlich sowohl Kompilierungsfehler als auch Logikfehler. Wenn Sie ihn also nach Belieben verwenden können, testen Sie ihn, bevor Sie ihn verwenden.

Michael Anderson
quelle
4

Eine schnelle Google-Suche liefert kludgey Lösungen wie diese , die setjmp / longjmp verwenden, wie andere erwähnt haben. Nichts ist so einfach und elegant wie C ++ / Java's try / catch. Ich bin ziemlich angetan von Adas Ausnahmebehandlung.

Überprüfen Sie alles mit if-Anweisungen :)

James Adam
quelle
4

Dies ist setjmp/longjmpin C möglich. P99 verfügt über ein recht komfortables Toolset, das auch mit dem neuen Thread-Modell von C11 übereinstimmt.

Jens Gustedt
quelle
2

Dies ist eine weitere Möglichkeit zur Fehlerbehandlung in C, die leistungsfähiger ist als die Verwendung von setjmp / longjmp. Leider funktioniert es nicht mit MSVC, aber wenn nur die Verwendung von GCC / Clang eine Option ist, können Sie dies in Betracht ziehen. Insbesondere wird die Erweiterung "Label als Wert" verwendet, mit der Sie die Adresse eines Labels übernehmen, in einem Wert speichern und bedingungslos dorthin springen können. Ich werde es anhand eines Beispiels präsentieren:

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 allgemeinen Code in Definitionen umgestalten und so Ihr eigenes Fehlerbehandlungssystem effektiv 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;
}
Keebus
quelle
2

Warnung: Das Folgende ist nicht sehr schön, aber es macht den Job.

#include <stdio.h>
#include <stdlib.h>

typedef struct {
    unsigned int  id;
    char         *name;
    char         *msg;
} error;

#define _printerr(e, s, ...) fprintf(stderr, "\033[1m\033[37m" "%s:%d: " "\033[1m\033[31m" e ":" "\033[1m\033[37m" " ‘%s_error’ " "\033[0m" s "\n", __FILE__, __LINE__, (*__err)->name, ##__VA_ARGS__)
#define printerr(s, ...) _printerr("error", s, ##__VA_ARGS__)
#define printuncaughterr() _printerr("uncaught error", "%s", (*__err)->msg)

#define _errordef(n, _id) \
error* new_##n##_error_msg(char* msg) { \
    error* self = malloc(sizeof(error)); \
    self->id = _id; \
    self->name = #n; \
    self->msg = msg; \
    return self; \
} \
error* new_##n##_error() { return new_##n##_error_msg(""); }

#define errordef(n) _errordef(n, __COUNTER__ +1)

#define try(try_block, err, err_name, catch_block) { \
    error * err_name = NULL; \
    error ** __err = & err_name; \
    void __try_fn() try_block \
    __try_fn(); \
    void __catch_fn() { \
        if (err_name == NULL) return; \
        unsigned int __##err_name##_id = new_##err##_error()->id; \
        if (__##err_name##_id != 0 && __##err_name##_id != err_name->id) \
            printuncaughterr(); \
        else if (__##err_name##_id != 0 || __##err_name##_id != err_name->id) \
            catch_block \
    } \
    __catch_fn(); \
}

#define throw(e) { *__err = e; return; }

_errordef(any, 0)

Verwendung:

errordef(my_err1)
errordef(my_err2)

try ({
    printf("Helloo\n");
    throw(new_my_err1_error_msg("hiiiii!"));
    printf("This will not be printed!\n");
}, /*catch*/ any, e, {
    printf("My lovely error: %s %s\n", e->name, e->msg);
})

printf("\n");

try ({
    printf("Helloo\n");
    throw(new_my_err2_error_msg("my msg!"));
    printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
    printerr("%s", e->msg);
})

printf("\n");

try ({
    printf("Helloo\n");
    throw(new_my_err1_error());
    printf("This will not be printed!\n");
}, /*catch*/ my_err2, e, {
    printf("Catch %s if you can!\n", e->name);
})

Ausgabe:

Helloo
My lovely error: my_err1 hiiiii!

Helloo
/home/naheel/Desktop/aa.c:28: error: my_err2_error my msg!

Helloo
/home/naheel/Desktop/aa.c:38: uncaught error: my_err1_error 

Beachten Sie, dass hierfür verschachtelte Funktionen und verwendet werden __COUNTER__. Sie sind auf der sicheren Seite, wenn Sie gcc verwenden.

Naheel
quelle
1

Redis simulieren mit goto try / catch. IMHO ist es sehr sauber und elegant:

/* Save the DB on disk. Return REDIS_ERR on error, REDIS_OK on success. */
int rdbSave(char *filename) {
    char tmpfile[256];
    FILE *fp;
    rio rdb;
    int error = 0;

    snprintf(tmpfile,256,"temp-%d.rdb", (int) getpid());
    fp = fopen(tmpfile,"w");
    if (!fp) {
        redisLog(REDIS_WARNING, "Failed opening .rdb for saving: %s",
            strerror(errno));
        return REDIS_ERR;
    }

    rioInitWithFile(&rdb,fp);
    if (rdbSaveRio(&rdb,&error) == REDIS_ERR) {
        errno = error;
        goto werr;
    }

    /* Make sure data will not remain on the OS's output buffers */
    if (fflush(fp) == EOF) goto werr;
    if (fsync(fileno(fp)) == -1) goto werr;
    if (fclose(fp) == EOF) goto werr;

    /* Use RENAME to make sure the DB file is changed atomically only
     * if the generate DB file is ok. */
    if (rename(tmpfile,filename) == -1) {
        redisLog(REDIS_WARNING,"Error moving temp DB file on the final destination: %s", strerror(errno));
        unlink(tmpfile);
        return REDIS_ERR;
    }
    redisLog(REDIS_NOTICE,"DB saved on disk");
    server.dirty = 0;
    server.lastsave = time(NULL);
    server.lastbgsave_status = REDIS_OK;
    return REDIS_OK;

werr:
    fclose(fp);
    unlink(tmpfile);
    redisLog(REDIS_WARNING,"Write error saving DB on disk: %s", strerror(errno));
    return REDIS_ERR;
}
Forrest Ye
quelle
Der Code ist kaputt. errnodarf nur unmittelbar nach dem fehlgeschlagenen Systemaufruf und nicht drei Aufrufe später verwendet werden.
Ceving
Dieser Code dupliziert die Fehlerbehandlungslogik an mehreren Stellen und führt möglicherweise falsche Aktionen aus, z. B. das mehrfache Aufrufen von fclose (fp). Es wäre weitaus besser, mehrere Beschriftungen zu verwenden und zu codieren, was noch zurückgefordert werden muss, indem diese Beschriftungen verwendet werden (und nicht nur eine für alle Fehler), und dann an die richtige Stelle für die Fehlerbehandlung zu springen, je nachdem, wo im Code der Fehler auftritt.
jschultz410
1

In C können Sie Ausnahmen zusammen mit der automatischen "Objektrückgewinnung" durch manuelle Verwendung von if + goto für die explizite Fehlerbehandlung "simulieren".

Ich schreibe oft C-Code wie den folgenden (zusammengefasst, um die Fehlerbehandlung hervorzuheben):

#include <assert.h>

typedef int errcode;

errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
    errcode ret = 0;

    if ( ( ret = foo_init( f ) ) )
        goto FAIL;

    if ( ( ret = goo_init( g ) ) )
        goto FAIL_F;

    if ( ( ret = poo_init( p ) ) )
        goto FAIL_G;

    if ( ( ret = loo_init( l ) ) )
        goto FAIL_P;

    assert( 0 == ret );
    goto END;

    /* error handling and return */

    /* Note that we finalize in opposite order of initialization because we are unwinding a *STACK* of initialized objects */

FAIL_P:
    poo_fini( p );

FAIL_G:
    goo_fini( g );

FAIL_F:
    foo_fini( f );

FAIL:
    assert( 0 != ret );

END:
    return ret;        
}

Dies ist völlig normales ANSI C, trennt die Fehlerbehandlung von Ihrem Hauptzeilencode, ermöglicht das (manuelle) Abwickeln von initialisierten Objekten wie in C ++ und es ist völlig offensichtlich, was hier passiert. Da Sie an jedem Punkt explizit auf Fehler testen, ist es einfacher, an jeder Stelle, an der ein Fehler auftreten kann, eine bestimmte Protokollierung oder Fehlerbehandlung einzufügen.

Wenn Ihnen ein wenig Makromagie nichts ausmacht, können Sie dies präziser gestalten, während Sie andere Dinge wie das Protokollieren von Fehlern mit Stapelspuren ausführen. Beispielsweise:

#include <assert.h>
#include <stdio.h>
#include <string.h>

#define TRY( X, LABEL ) do { if ( ( X ) ) { fprintf( stderr, "%s:%d: Statement '" #X "' failed! %d, %s\n", __FILE__, __LINE__, ret, strerror( ret ) ); goto LABEL; } while ( 0 )

typedef int errcode;

errcode init_or_fail( foo *f, goo *g, poo *p, loo *l )
{
    errcode ret = 0;

    TRY( ret = foo_init( f ), FAIL );
    TRY( ret = goo_init( g ), FAIL_F );
    TRY( ret = poo_init( p ), FAIL_G );
    TRY( ret = loo_init( l ), FAIL_P );

    assert( 0 == ret );
    goto END;

    /* error handling and return */

FAIL_P:
    poo_fini( p );

FAIL_G:
    goo_fini( g );

FAIL_F:
    foo_fini( f );

FAIL:
    assert( 0 != ret );

END:
    return ret;        
}

Dies ist natürlich nicht so elegant wie C ++ - Ausnahmen + Destruktoren. Das Verschachteln mehrerer Fehlerbehandlungsstapel in einer Funktion auf diese Weise ist beispielsweise nicht sehr sauber. Stattdessen möchten Sie diese wahrscheinlich in eigenständige Unterfunktionen aufteilen, die Fehler auf ähnliche Weise behandeln. Initialisieren und finalisieren Sie dies explizit.

Dies funktioniert auch nur innerhalb einer einzelnen Funktion und springt nicht weiter in den Stapel, es sei denn, Aufrufer höherer Ebenen implementieren eine ähnliche explizite Fehlerbehandlungslogik, während eine C ++ - Ausnahme den Stapel nur so lange hochspringt, bis ein geeigneter Handler gefunden wird. Sie können auch keinen beliebigen Typ auslösen, sondern nur einen Fehlercode.

Das systematische Codieren auf diese Weise (dh mit einem einzelnen Eintritts- und einem einzigen Austrittspunkt) macht es auch sehr einfach, Pre- und Post-Logik ("endlich") einzufügen, die unabhängig von der jeweiligen Ausführung ausgeführt wird. Sie setzen einfach Ihre "Endlich" -Logik nach dem END-Label.

jschultz410
quelle
1
Sehr schön. Ich neige dazu, etwas Ähnliches zu tun. goto ist großartig für dieses Szenario. Der einzige Unterschied ist, dass ich die Notwendigkeit für dieses letzte "goto END" nicht sehe. Ich füge an diesem Punkt einfach eine Erfolgsrückgabe ein, eine Fehlerrückgabe nach dem Rest.
Neil Roy
1
Danke @NeilRoy Der Grund für das goto END ist, dass ich die überwiegende Mehrheit meiner Funktionen mit einem einzigen Einstiegspunkt und einem einzigen Austrittspunkt mag. Auf diese Weise lauert irgendwo eine andere versteckte Rendite, wenn ich einer Funktion eine "endgültige" Logik hinzufügen möchte, die ich immer problemlos ausführen kann, ohne mir Sorgen machen zu müssen. :)
jschultz410
0

Wenn Sie C mit Win32 verwenden, können Sie das Structured Exception Handling (SEH) nutzen , um Try / Catch zu simulieren.

Wenn Sie C auf Plattformen verwenden, die dies nicht unterstützen, setjmp()und longjmp()sich diese Ausnahmebehandlung der pjsip-Bibliothek ansehen, bietet sie eine eigene Implementierung

onmyway133
quelle
-1

Vielleicht keine Hauptsprache (leider), aber in APL gibt es die ⎕EA-Operation (steht für Execute Alternate).

Verwendung: 'Y' ⎕EA 'X' wobei X und Y entweder Codefragmente sind, die als Zeichenfolgen oder Funktionsnamen geliefert werden.

Wenn X auf einen Fehler stößt, wird stattdessen Y (normalerweise Fehlerbehandlung) ausgeführt.

Mappo
quelle
2
Hallo Mappo, willkommen bei StackOverflow. Obwohl die Frage interessant war, ging es speziell darum, dies in C zu tun. Dies beantwortet die Frage
Luser Droog