Wypróbuj instrukcje catch w języku C

101

Myślałem dzisiaj o blokach try / catch istniejących w innych językach. Przez chwilę googlowałem, ale bez rezultatu. Z tego, co wiem, nie ma czegoś takiego jak try / catch w C. Czy jest jednak sposób, aby je „zasymulować”?
Jasne, jest asercja i inne sztuczki, ale nic takiego jak try / catch, które również łapią podniesiony wyjątek. Dziękuję Ci

Andrzej
źródło
3
Mechanizmy podobne do wyjątków nie będą ogólnie przydatne bez mechanizmu automatycznego zwalniania zasobów, gdy stos jest rozwijany. C ++ używa RAII; Java, C #, Python itp. Używają garbage collectorów. (I pamiętaj, że odśmiecacze pamięci
zwalniają
@jamesdlin, Dlaczego nie moglibyśmy zrobić RAII z ​​C?
Pacerier
1
@Pacerier RAII wymaga automatycznego wywoływania funkcji po zniszczeniu obiektów (np. Destruktorów). Jak zamierzasz to zrobić w C?
jamesdlin

Odpowiedzi:

90

Samo C nie obsługuje wyjątków, ale można je do pewnego stopnia symulować za pomocą setjmpi longjmpwywołań.

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);
}

Ta strona internetowa zawiera fajny poradnik, jak symulować wyjątki za pomocą setjmpilongjmp

JaredPar
źródło
1
super rozwiązanie! czy to rozwiązanie jest krzyżowe? To działało dla mnie na MSVC2012, ale nie w kompilatorze MacOSX Clang.
mannysz
1
clue me in: Myślę, że klauzule try catch pozwalają na wychwytywanie wyjątków (takich jak dzielenie przez zero). Ta funkcja wydaje się zezwalać tylko na wychwytywanie wyjątków, które sam rzucasz. Prawdziwe wyjątki nie są generowane przez wywołanie longjmp, prawda? Jeśli użyję tego kodu do zrobienia czegoś takiego, try{ x = 7 / 0; } catch(divideByZeroException) {print('divided by zero')}; to nie zadziała, prawda?
Sam
Dzielenie przez zero nie jest nawet wyjątkiem w C ++, aby sobie z tym poradzić, musisz albo sprawdzić, czy dzielnik nie jest zerem i obsłużyć go, albo obsłużyć SIGFPE, który jest generowany podczas wykonywania formuły dzielenia przez zero.
James,
25

Używasz goto w C w podobnych sytuacjach obsługi błędów.
To najbliższy odpowiednik wyjątków, jaki można uzyskać w C.

Alok Save
źródło
3
@JensGustedt To jest dokładnie to, do czego goto jest obecnie używane bardzo często i gdzie ma to sens (setjmp / ljmp jest lepszą alternatywą, ale label + goto jest zwykle częściej używany).
Tomas Pruzina
1
@AoeAoe, prawdopodobnie gotojest bardziej używany do obsługi błędów, ale co z tego? Pytanie nie dotyczy obsługi błędów jako takiej, ale wyraźnie dotyczy odpowiedników try / catch. gotonie jest odpowiednikiem try / catch, ponieważ jest ograniczone do tej samej funkcji.
Jens Gustedt
@JensGustedt W pewnym sensie zareagowałem na nienawiść / strach przed goto i ludźmi, którzy go używają (moi nauczyciele opowiadali mi też przerażające historie o używaniu go na uniwersytecie). [OT] Jedyną rzeczą, która jest naprawdę, naprawdę ryzykowna i „mętna” w goto, jest „przechodzenie do tyłu”, ale widziałem to w Linux VFS (git blame guy przysięgał, że jest to korzystne dla wydajności).
Tomas Pruzina,
Zobacz źródła systemctl w celu uzyskania legalnych zastosowań gotojako mechanizmu try / catch używanego w nowoczesnym, szeroko akceptowanym, recenzowanym źródle. Wyszukaj gotoodpowiednik „rzut” i finishodpowiednik „złapania”.
Stewart
13

Ok, nie mogłem się powstrzymać od odpowiedzi na to. Pozwólcie, że najpierw powiem, że nie uważam, aby symulowanie tego w C nie było dobrym pomysłem, ponieważ jest to naprawdę obca koncepcja dla C.

Możemy użyć nadużyć preprocesora i lokalnych zmiennych stosu, aby dać użycie ograniczonej wersji C ++ try / throw / catch.

Wersja 1 (rzuty zakresu lokalnego)

#include <stdbool.h>

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

Wersja 1 jest tylko rzutem lokalnym (nie można opuścić zakresu funkcji). Opiera się na zdolności C99 do deklarowania zmiennych w kodzie (powinno działać w C89, jeśli próba jest pierwszą rzeczą w funkcji).

Ta funkcja po prostu tworzy lokalną zmienną, więc wie, czy wystąpił błąd i używa goto, aby przeskoczyć do bloku catch.

Na przykład:

#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;
}

To działa na przykład:

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

Wersja 2 (skoki przez lunetę)

#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);

Wersja 2 jest dużo bardziej złożona, ale zasadniczo działa w ten sam sposób. Wykorzystuje długi skok z bieżącej funkcji do bloku try. Blok try używa następnie if / else, aby przeskoczyć blok kodu do bloku catch, który sprawdza zmienną lokalną, aby zobaczyć, czy powinna przechwycić.

Przykład ponownie się rozwinął:

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;
}

Używa globalnego wskaźnika, więc longjmp () wie, jaka próba została ostatnio uruchomiona. Jesteśmy przy użyciu nadużywa stos tak funkcjonuje dziecko może mieć również blok try / catch.

Korzystanie z tego kodu ma wiele wad (ale jest fajnym ćwiczeniem umysłowym):

  • Nie zwalnia przydzielonej pamięci, ponieważ nie ma wywoływanych dekonstruktorów.
  • Nie możesz mieć więcej niż 1 próba / przechwytywanie w zakresie (bez zagnieżdżania)
  • W rzeczywistości nie można rzucać wyjątków ani innych danych, takich jak w C ++
  • W ogóle nie jest bezpieczny wątkowo
  • Skonfigurujesz innych programistów pod kątem awarii, ponieważ prawdopodobnie nie zauważą włamania i spróbują ich używać jak bloków try / catch w C ++.
Paul Hutchinson
źródło
ładne alternatywne rozwiązania.
HaseeB Mir
wersja 1 jest fajnym pomysłem, ale zmienna __HadError musiałaby zostać zresetowana lub objęta zakresem. W przeciwnym razie nie będziesz mógł użyć więcej niż jednego try-catch w tym samym bloku. Może użyj funkcji globalnej, takiej jak bool __ErrorCheck(bool &e){bool _e = e;e=false;return _e;}. Ale zmienna lokalna również zostałaby ponownie zdefiniowana, więc sprawy wymykają się spod kontroli.
flamewave000
Tak, jest ograniczone do jednego try-catch w tej samej funkcji. Większym problemem niż zmienna jest jednak etykieta, ponieważ nie można mieć zduplikowanych etykiet w tej samej funkcji.
Paul Hutchinson
10

W C99 możesz użyć setjmp/longjmp dla nielokalnego przepływu sterowania.

W ramach jednego zakresu ogólny, ustrukturyzowany wzorzec kodowania dla języka C w obecności wielu alokacji zasobów i wielu wyjść goto, jak w tym przykładzie . Jest to podobne do tego, jak C ++ implementuje wywołania destruktorów automatycznych obiektów pod maską i jeśli będziesz się tego pilnie trzymał, powinno to pozwolić na pewien stopień czystości nawet w złożonych funkcjach.

Kerrek SB
źródło
5

Chociaż niektóre z pozostałych odpowiedzi objęły proste przypadki korzystania setjmpi longjmp, w rzeczywistej aplikacji nie ma obawy, że dwa naprawdę sprawa.

  1. Zagnieżdżanie bloków try / catch. Korzystanie z jednej zmiennej globalnej dla Twojegojmp_buf spowoduje, że nie będą one działać.
  2. Gwintowanie. Pojedyncza zmienna globalna jmp_bufspowoduje w tej sytuacji wszelkiego rodzaju ból.

Rozwiązaniem tego problemu jest utrzymanie lokalnego stosu wątków jmp_buf który jest aktualizowany na bieżąco. (Myślę, że właśnie tego używa lua wewnętrznie).

Więc zamiast tego (z niesamowitej odpowiedzi JaredPara)

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);
}

Użyłbyś czegoś takiego:

#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);
}

Ponownie, bardziej realistyczna wersja tego zawierałaby sposób na przechowywanie informacji o błędach w pliku exception_state, lepsza obsługaMAX_EXCEPTION_DEPTH pliku (może użycie realloc w celu powiększenia bufora lub coś w tym rodzaju).

ZRZECZENIE SIĘ: Powyższy kod został napisany bez jakichkolwiek testów. Jest to wyłącznie po to, abyś miał pojęcie, jak uporządkować rzeczy. Różne systemy i różne kompilatory będą musiały inaczej implementować pamięć lokalną wątku. Kod prawdopodobnie zawiera zarówno błędy kompilacji, jak i błędy logiczne - więc jeśli możesz go używać według własnego uznania, PRZETESTUJ go przed użyciem;)

Michael Anderson
źródło
4

Szybkie wyszukiwanie w Google daje niezłe rozwiązania, takie jak to które używają setjmp / longjmp, jak wspominali inni. Nie ma nic tak prostego i eleganckiego jak try / catch w C ++ / Javie. Jestem raczej stronniczy w kwestii obsługi wyjątków przez Adę.

Sprawdź wszystko za pomocą instrukcji if :)

James Adam
źródło
4

Można to zrobić setjmp/longjmpw C. P99 ma do tego całkiem wygodny zestaw narzędzi, który jest również zgodny z nowym modelem gwintu C11.

Jens Gustedt
źródło
2

To kolejny sposób obsługi błędów w C, który jest bardziej wydajny niż użycie setjmp / longjmp. Niestety, nie będzie działać z MSVC, ale jeśli używasz tylko GCC / Clang, możesz to rozważyć. W szczególności używa rozszerzenia „etykieta jako wartość”, które umożliwia pobranie adresu etykiety, zapisanie jej w wartości i bezwarunkowy przeskok do niej. Przedstawię to na przykładzie:

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;
}

Jeśli chcesz, możesz refaktoryzować wspólny kod w definicjach, skutecznie wdrażając własny system obsługi błędów.

/* 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); }

Wtedy staje się przykład

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
źródło
2

Ostrzeżenie: poniższe nie są zbyt miłe, ale spełniają swoje zadanie.

#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)

Stosowanie:

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);
})

Wynik:

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 

Pamiętaj, że jest to użycie zagnieżdżonych funkcji i __COUNTER__. Będziesz po bezpiecznej stronie, jeśli używasz gcc.

Naheel
źródło
1

Redis używa goto do symulacji try / catch, IMHO jest bardzo czysty i elegancki:

/* 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
źródło
Kod jest uszkodzony. errnonależy używać tylko po nieudanym wywołaniu systemowym, a nie trzy wywołania później.
ceving
Ten kod powiela logikę obsługi błędów w wielu miejscach i może powodować niepoprawne rzeczy, takie jak wielokrotne wywoływanie fclose (fp). Znacznie lepiej byłoby użyć wielu etykiet i zakodować to, co nadal wymaga odzyskania przy użyciu tych etykiet (zamiast tylko jednej dla wszystkich błędów), a następnie przejść do właściwego miejsca obsługi błędów w zależności od tego, gdzie w kodzie występuje błąd.
jschultz410
1

W C można „symulować” wyjątki wraz z automatycznym „odzyskiwaniem obiektów” poprzez ręczne użycie if + goto do jawnej obsługi błędów.

Często piszę kod w C, jak poniżej (sprowadzając się do obsługi błędów):

#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;        
}

Jest to całkowicie standardowe ANSI C, oddziela obsługę błędów od głównego kodu, umożliwia (ręczne) rozwijanie stosu zainicjowanych obiektów, podobnie jak C ++, i jest całkowicie oczywiste, co się tutaj dzieje. Ponieważ w każdym momencie jawnie testujesz błąd, ułatwia to wstawianie określonego rejestrowania lub obsługi błędów w każdym miejscu, w którym może wystąpić błąd.

Jeśli nie masz nic przeciwko odrobinie magii makr, możesz uczynić to bardziej zwięzłym, wykonując inne czynności, takie jak rejestrowanie błędów ze śladami stosu. Na przykład:

#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;        
}

Oczywiście nie jest to tak eleganckie, jak wyjątki C ++ + destruktory. Na przykład zagnieżdżanie wielu stosów obsługi błędów w jednej funkcji w ten sposób nie jest zbyt czyste. Zamiast tego prawdopodobnie chciałbyś podzielić je na niezależne funkcje podrzędne, które podobnie obsługują błędy, inicjalizuj + finalizuj jawnie w ten sposób.

Działa to również tylko w ramach pojedynczej funkcji i nie będzie przeskakiwać w górę stosu, chyba że wywołania wyższego poziomu zaimplementują podobną jawną logikę obsługi błędów, podczas gdy wyjątek C ++ będzie po prostu przeskakiwał stos, dopóki nie znajdzie odpowiedniego programu obsługi. Nie pozwala też na wyrzucenie dowolnego typu, ale zamiast tego tylko kod błędu.

Systematyczne kodowanie w ten sposób (tj. - z pojedynczym wejściem i pojedynczym punktem wyjścia) również bardzo ułatwia wstawianie logiki pre i post („ostatecznie”), która będzie wykonywana bez względu na wszystko. Po etykiecie END umieszczasz swoją logikę „ostatecznie”.

jschultz410
źródło
1
Bardzo dobrze. Zwykle robię coś podobnego. Goto świetnie sprawdza się w tym scenariuszu. Jedyną różnicą jest to, że nie widzę potrzeby ostatniego „goto END”, po prostu wstawiam w tym momencie powrót sukcesu, a po reszcie powrót niepowodzenia.
Neil Roy
1
Dzięki @NeilRoy Powodem goto END jest to, że lubię, aby większość moich funkcji miała jeden punkt wejścia i jeden punkt wyjścia. W ten sposób, jeśli chcę dodać logikę „wreszcie” do dowolnej funkcji, którą zawsze mogę z łatwością, bez martwienia się, gdzieś czają się inne ukryte zwroty. :)
jschultz410
0

Jeśli używasz C z Win32, możesz wykorzystać jego Structured Exception Handling (SEH) do symulacji try / catch.

Jeśli używasz C na platformach, które nie obsługują setjmp()i longjmp(), spójrz na tę obsługę wyjątków biblioteki pjsip, zapewnia ona własną implementację

onmyway133
źródło
-1

Być może nie jest to główny język (niestety), ale w APL istnieje operacja ⎕EA (skrót od Execute Alternate).

Sposób użycia: „Y” ⎕EA „X”, gdzie X i Y to fragmenty kodu dostarczane jako ciągi znaków lub nazwy funkcji.

Jeśli X napotka błąd, zamiast tego zostanie wykonana Y (zwykle obsługa błędów).

mappo
źródło
2
Cześć, mappo, witaj w StackOverflow. Chociaż interesujące, pytanie dotyczyło konkretnie robienia tego w C. Więc to tak naprawdę nie odpowiada na pytanie.
luser droog