Obsługa błędów w kodzie C

Co uważasz za "najlepszą praktykę", jeśli chodzi o obsługę błędów w spójny sposób w bibliotece C.

Są dwa sposoby, o których myślałem:

Zawsze zwracaj kod błędu. Typowa funkcja wyglądałaby tak:

MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);

Podejście always provide an error pointer:

int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);

Przy pierwszym podejściu możliwe jest napisanie kodu w ten sposób, w którym sprawdzanie obsługi błędów jest bezpośrednio umieszczane w funkcji wywołanie:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

Który wygląda lepiej niż kod obsługi błędów tutaj.

MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
    // Error handling
}

Myślę jednak, że użycie zwracanej wartości do zwracania danych sprawia, że kod jest bardziej czytelny, oczywiste jest, że coś zostało napisane do zmiennej size w drugim przykładzie.

Czy masz jakieś pomysły, dlaczego powinienem preferować którekolwiek z tych podejść, a może mieszać je lub używać czegoś innego? Nie jestem fanem globalnych Stanów błędów, ponieważ ma tendencję do wielowątkowego korzystania z biblioteki bolesne.

Edytuj: C++ konkretne pomysły na ten temat również byłoby interesujące usłyszeć o tak długo, jak nie są one związane z wyjątkami, ponieważ nie jest to opcja dla mnie w tej chwili...

Author: ubershmekel, 2008-12-22

23 answers

Podoba mi się błąd jako sposób zwracania wartości. Jeśli projektujesz api i chcesz korzystać z biblioteki jak najbardziej bezbolesnie, pomyśl o tych dodatkach:

  • Zapisz wszystkie możliwe Stany błędów w jednym typedef ' ed enum i użyj go w lib. Nie zwracaj tylko ints lub co gorsza, mieszaj ints lub różne wyliczenia z kodami zwrotnymi.

  • Zapewnij funkcję, która przekształca błędy w coś czytelnego dla człowieka. To może być proste. Just error-enum in, const char* Wynocha.

  • Wiem, że ten pomysł sprawia, że użycie wielowątkowe jest nieco trudne, ale byłoby miło, gdyby programista aplikacji mógł ustawić globalny błąd-callback. W ten sposób będą mogli umieścić punkt przerwania w oddzwanianiu podczas sesji wyszukiwania błędów.

Mam nadzieję, że to pomoże.

 76
Author: Nils Pipenbrinck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:01:46

Użyłem obu podejść, i oba działały dobrze dla mnie. Niezależnie od tego, którego używam, zawsze staram się stosować tę zasadę:

Jeśli jedynymi możliwymi błędami są błędy programisty, nie zwracaj kodu błędu, użyj asserts wewnątrz funkcji.

Twierdzenie, które waliduje dane wejściowe, jasno komunikuje, czego oczekuje funkcja, podczas gdy zbyt duże sprawdzanie błędów może zaciemnić logikę programu. Decydowanie, co zrobić dla wszystkich różnych przypadków błędów może naprawdę skomplikować projekt. Po co zastanawiać się, jak functionX powinien obsługiwać wskaźnik null, jeśli zamiast tego możesz nalegać, aby programista nigdy go nie zdał?

 94
Author: AShelly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-15 19:13:37

Jest ładny zestaw slajdów z CERT CMU z zaleceniami, kiedy używać każdej z popularnych technik obsługi błędów C (i C++). Jednym z najlepszych slajdów jest to drzewo decyzyjne:

Drzewo Decyzyjne Obsługi Błędów

[1]} osobiście zmieniłbym dwie rzeczy na temat tego flowcarta.

Po Pierwsze, chciałbym wyjaśnić, że czasami obiekty powinny używać wartości zwracanych, aby wskazać błędy. Jeśli funkcja tylko wyodrębnia dane z obiektu, ale nie mutuje obiektu, to integralność sam obiekt nie jest zagrożony i wskazywanie błędów za pomocą wartości zwrotnej jest bardziej odpowiednie.

Po drugie, nie jest zawsze właściwe używanie WYJĄTKÓW w C++. Wyjątki są dobre, ponieważ mogą zmniejszyć ilość kodu źródłowego poświęconego obsłudze błędów, w większości nie wpływają na sygnatury funkcji i są bardzo elastyczne w tym, jakie dane mogą przekazać do callstack. Z drugiej strony wyjątki mogą nie być właściwym wyborem dla kilku uzasadnienie:

  1. Wyjątki C++ mają bardzo specyficzną semantykę. Jeśli nie chcesz tych semantyki, to wyjątki w C++ są złym wyborem. Wyjątek musi być rozpatrywany natychmiast po wyrzuceniu, a projekt sprzyja przypadkowi, w którym błąd będzie musiał rozluźnić callstack o kilka poziomów.

  2. Funkcje C++, które wyrzucają wyjątki, nie mogą być później owinięte tak, aby nie wyrzucać WYJĄTKÓW, przynajmniej nie bez zapłacenia pełnego kosztu WYJĄTKÓW. Funkcje, które kody błędów zwrotnych mogą być owinięte, aby rzucać wyjątki w C++, co czyni je bardziej elastycznymi. C++'S new dostaje to prawo poprzez podanie wariantu nie rzucającego.

  3. Wyjątki w C++ są stosunkowo drogie, ale ten minus jest w większości przeceniany przez programy korzystające z WYJĄTKÓW. Program po prostu nie powinien umieszczać WYJĄTKÓW na ścieżce kodowej, gdzie wydajność jest problemem. Nie ma znaczenia, jak szybko twój program może zgłosić błąd i zakończyć działanie.

  4. Czasami Wyjątki w C++ nie są dostępne. Albo są one dosłownie niedostępne w implementacji C++, albo wytyczne kodu ich zakazują.


Ponieważ pierwotne pytanie dotyczyło kontekstu wielowątkowego, myślę, że technika lokalnego wskaźnika błędów (opisana w odpowiedzi Sirdariusa ) Była niedoceniana w oryginalnych odpowiedziach. Jest to threadsafe, nie wymusza natychmiastowego usunięcia błędu przez wywołującego i może wiązać dowolne dane opisując błąd. Minusem jest to, że musi być utrzymywany przez obiekt (lub przypuszczam, że w jakiś sposób powiązany zewnętrznie) i jest prawdopodobnie łatwiejsze do zignorowania niż kod powrotu.

 29
Author: Praxeolitic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-21 17:44:14

Używam pierwszego podejścia za każdym razem, gdy tworzę bibliotekę. Istnieje kilka zalet korzystania z typedef ' ed enum jako kodu zwrotnego.

  • Jeśli funkcja zwraca bardziej skomplikowane wyjście, takie jak tablica i jej długość, nie musisz tworzyć dowolnych struktur, aby je zwrócić.

    rc = func(..., int **return_array, size_t *array_length);
    
  • Pozwala na prostą, ustandaryzowaną obsługę błędów.

    if ((rc = func(...)) != API_SUCCESS) {
       /* Error Handling */
    }
    
  • Pozwala na prostą obsługę błędów w bibliotece funkcja.

    /* Check for valid arguments */
    if (NULL == return_array || NULL == array_length)
        return API_INVALID_ARGS;
    
  • Użycie typedef ' ed enum pozwala również na wyświetlenie nazwy enum w debuggerze. Pozwala to na łatwiejsze debugowanie bez konieczności ciągłej konsultacji z plikiem nagłówkowym. Pomocna jest również funkcja tłumacząca to enum na łańcuch znaków.

Najważniejszą kwestią niezależnie od zastosowanego podejścia jest spójność. Dotyczy to nazewnictwa funkcji i argumentów, porządkowania argumentów i obsługi błędów.

 19
Author: Jeffrey Cohen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-23 05:38:20

Użyj setjmp .

Http://en.wikipedia.org/wiki/Setjmp.h

Http://aszt.inf.elte.hu / ~gsd/halado_cpp/ch02s03.html

Http://www.di.unipi.it / ~nids/docs/longjump_try_trow_catch.html

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

jmp_buf x;

void f()
{
    longjmp(x,5); // throw 5;
}

int main()
{
    // output of this program is 5.

    int i = 0;

    if ( (i = setjmp(x)) == 0 )// try{
    {
        f();
    } // } --> end of try{
    else // catch(i){
    {
        switch( i )
        {
        case  1:
        case  2:
        default: fprintf( stdout, "error code = %d\n", i); break;
        }
    } // } --> end of catch(i){
    return 0;
}

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

#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)

int
main(int argc, char** argv)
{
   TRY
   {
      printf("In Try Statement\n");
      THROW;
      printf("I do not appear\n");
   }
   CATCH
   {
      printf("Got Exception!\n");
   }
   ETRY;

   return 0;
}
 9
Author: Amir Saniyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-04-19 19:38:21

Zwracanie kodu błędu jest standardowym podejściem do obsługi błędów w C.

Ale ostatnio eksperymentowaliśmy również z wychodzącym wskaźnikiem błędów.

Ma pewne zalety w stosunku do metody zwracanej wartości:

  • Zwracaną wartość można wykorzystać do bardziej znaczących celów.

  • Konieczność wypisania tego parametru błędu przypomina, aby obsłużyć błąd lub propagować go. (Nigdy nie zapominasz o sprawdzeniu zwracanej wartości fclose, nie ty?)

  • Jeśli używasz wskaźnika błędu, możesz przekazać go w dół podczas wywoływania funkcji. Jeśli którakolwiek z funkcji go ustawi, wartość nie zostanie utracona.

  • Ustawiając punkt przerwania danych w zmiennej error, możesz wykryć, gdzie wystąpił błąd jako pierwszy. Ustawiając warunkowy punkt przerwania, możesz także wychwycić określone błędy.

  • Ułatwia to automatyzację sprawdzania, czy obsługujesz wszystkie błędy. Konwencja Kodeksu może zmusić cię do wywołania swojego wskaźnik błędu jako {[1] } I musi być ostatnim argumentem. Więc skrypt może dopasować łańcuch err);, a następnie sprawdzić, czy po nim następuje if (*err. W praktyce wykonaliśmy makro o nazwie CER (check err return) oraz CEG (check err goto). Nie musisz więc wpisywać go zawsze, gdy chcemy tylko powrócić na błąd i możemy zmniejszyć bałagan wizualny.

Nie wszystkie funkcje w naszym kodzie mają ten parametr wychodzący. Ten parametr wychodzący jest używany w przypadkach, w których normalnie rzuciłby wyjątek.

 9
Author: Calmarius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-21 13:58:47

Osobiście wolę pierwsze podejście (zwracanie wskaźnika błędu).

W razie potrzeby wynik zwrotu powinien po prostu wskazywać, że wystąpił błąd, z inną funkcją używaną do znalezienia dokładnego błędu.

W twoim przykładzie getSize() rozważyłbym, że rozmiary muszą być zawsze zerowe lub dodatnie, więc zwrócenie negatywnego wyniku może wskazywać na błąd, podobnie jak wywołania systemu UNIX.

Nie przychodzi mi do głowy Żadna biblioteka, z której korzystałem, która pasuje do tej drugiej podejście z obiektem błędu przekazanym jako wskaźnik. stdio, itd. wszystkie mają wartość zwracaną.

 7
Author: Alnitak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:00:07

Podejście uniksowe jest najbardziej podobne do twojej drugiej sugestii. Zwraca wynik lub pojedynczą wartość "poszło źle". Na przykład open zwróci deskryptor pliku po pomyślnym zakończeniu lub -1 po niepowodzeniu. W przypadku awarii ustawia również errno, zewnętrzną globalną liczbę całkowitą wskazującą , która wystąpiła awaria.

Jeśli to coś warte, kakao również przyjęło podobne podejście. Wiele metod zwraca BOOL i przyjmuje parametr NSError **, tak że po niepowodzeniu ustawiają błąd i return NO. Następnie obsługa błędów wygląda następująco:

NSError *error = nil;
if ([myThing doThingError: &error] == NO)
{
  // error handling
}

Który jest gdzieś pomiędzy dwoma opcjami: -).

 7
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:12:26

Kiedy piszę programy, podczas inicjalizacji, Zwykle wyłączam wątek do obsługi błędów i inicjalizuję specjalną strukturę dla błędów, w tym blokadę. Następnie, gdy wykryję błąd, poprzez zwracane wartości, wprowadzam informacje z wyjątku do struktury i wysyłam SIGIO do wątku obsługi wyjątków, a następnie sprawdzam, czy nie mogę kontynuować wykonywania. Jeśli nie mogę, wysyłam SIGURGA do wątku wyjątkowego, który z wdziękiem zatrzymuje program.

 7
Author: Henry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-08 14:05:28

Oto prosty program do zademonstrowania dwóch pierwszych punktów odpowiedzi Nilsa Pipenbrincka .

Jego pierwsze 2 kule to:

  • Przechowuj wszystkie możliwe Stany błędów w jednym typedef ' ed enum i używaj go w swojej lib. Nie zwracaj tylko ints lub co gorsza, mieszaj ints lub różne wyliczenia z kodami zwrotnymi.

  • Zapewnij funkcję, która przekształca błędy w coś czytelnego dla człowieka. To może być proste. Just error-enum in, const char* Wynocha.

Załóżmy, że napisałeś moduł o nazwie mymodule. Po pierwsze, w mymodule.H, definiujesz kody błędów oparte na enum i piszesz kilka ciągów błędów, które odpowiadają tym kodom. tutaj używam tablicy ciągów C (char *), która działa dobrze tylko wtedy, gdy twój pierwszy kod błędu oparty na enum ma wartość 0, a następnie nie manipulujesz liczbami. jeśli używasz numerów kodów błędów z przerwami lub innymi wartościami początkowymi, po prostu musisz zmienić używanie mapowanej tablicy C-string (jak to robię poniżej) do użycia funkcji, która używa instrukcji switch lub instrukcji if / else if do mapowania z kodów błędów enum na drukowalne ciągi C (których nie demonstruję).Wybór należy do ciebie.

Mymodule.h

/// @brief Error codes for library "mymodule"
typedef enum mymodule_error_e
{
    /// No error
    MYMODULE_ERROR_OK = 0,
    
    /// Invalid arguments (ex: NULL pointer where a valid pointer is required)
    MYMODULE_ERROR_INVARG,

    /// Out of memory (RAM)
    MYMODULE_ERROR_NOMEM,

    /// Make up your error codes as you see fit
    MYMODULE_ERROR_MYERROR, 

    // etc etc
    
    /// Total # of errors in this list (NOT AN ACTUAL ERROR CODE);
    /// NOTE: that for this to work, it assumes your first error code is value 0 and you let it naturally 
    /// increment from there, as is done above, without explicitly altering any error values above
    MYMODULE_ERROR_COUNT,
} mymodule_error_t;

// Array of strings to map enum error types to printable strings
// - see important NOTE above!
const char* const MYMODULE_ERROR_STRS[] = 
{
    "MYMODULE_ERROR_OK",
    "MYMODULE_ERROR_INVARG",
    "MYMODULE_ERROR_NOMEM",
    "MYMODULE_ERROR_MYERROR",
};

// To get a printable error string
const char* mymodule_error_str(mymodule_error_t err);

// Other functions in mymodule
mymodule_error_t mymodule_func1(void);
mymodule_error_t mymodule_func2(void);
mymodule_error_t mymodule_func3(void);
Mymodule.c zawiera funkcję mapowania do mapowania z kodów błędów enum do drukowalnych ciągów C:

Mymodule.c

#include <stdio.h>

/// @brief      Function to get a printable string from an enum error type
/// @param[in]  err     a valid error code for this module
/// @return     A printable C string corresponding to the error code input above, or NULL if an invalid error code
///             was passed in
const char* mymodule_error_str(mymodule_error_t err)
{
    const char* err_str = NULL;

    // Ensure error codes are within the valid array index range
    if (err >= MYMODULE_ERROR_COUNT)
    {
        goto done;
    }

    err_str = MYMODULE_ERROR_STRS[err];

done:
    return err_str;
}

// Let's just make some empty dummy functions to return some errors; fill these in as appropriate for your 
// library module

mymodule_error_t mymodule_func1(void)
{
    return MYMODULE_ERROR_OK;
}

mymodule_error_t mymodule_func2(void)
{
    return MYMODULE_ERROR_INVARG;
}

mymodule_error_t mymodule_func3(void)
{
    return MYMODULE_ERROR_MYERROR;
}

Main.c zawiera program testowy, który zademonstruje wywołanie niektórych funkcji i wypisanie z nich niektórych kodów błędów:

Main.c

#include <stdio.h>

int main()
{
    printf("Demonstration of enum-based error codes in C (or C++)\n");

    printf("err code from mymodule_func1() = %s\n", mymodule_error_str(mymodule_func1()));
    printf("err code from mymodule_func2() = %s\n", mymodule_error_str(mymodule_func2()));
    printf("err code from mymodule_func3() = %s\n", mymodule_error_str(mymodule_func3()));

    return 0;
}

Wyjście:

W języku C (lub C++) Można używać kodów błędów opartych na enum.]} err code from mymodule_func1 () = MYMODULE_ERROR_OK
err code from mymodule_func2 () = MYMODULE_ERROR_INVARG
err code from mymodule_func3 () = MYMODULE_ERROR_MYERROR

Bibliografia:

Możesz uruchomić ten kod samodzielnie tutaj: https://onlinegdb.com/ByEbKLupS .

 7
Author: Gabriel Staples,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Robiłem wiele programowania w C w przeszłości. I naprawdę apreciated wartość zwracana kodu błędu. Ale is ma kilka możliwych pułapek:

  • Zduplikuj liczby błędów, można to rozwiązać za pomocą globalnego błędu.plik H.
  • zapominając sprawdzić kod błędu, powinno to być rozwiązane za pomocą cluebat i długich godzin debugowania. Ale w końcu nauczysz się (lub będziesz wiedział, że ktoś inny zrobi debugowanie).
 6
Author: Toon Krijthe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:00:53

Ostatnio też zastanawiałem się nad tym problemem i napisałem kilka makr dla języka C, które symulują semantykę try-catch-finally używając czysto lokalnych wartości zwracanych. Mam nadzieję, że się przyda.

 5
Author: naasking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-24 03:43:51

Oto podejście, które moim zdaniem jest interesujące, a jednocześnie wymaga pewnej dyscypliny.

Zakłada się, że zmienna typu handle jest instancją, na której działają wszystkie funkcje API.

Chodzi o to, że struktura za uchwytem przechowuje poprzedni błąd jako strukturę z niezbędnymi danymi (kod, wiadomość...), a użytkownik otrzymuje funkcję, która zwraca wskaźnik do tego obiektu błędu. Każda operacja zaktualizuje wskazywany obiekt, dzięki czemu użytkownik może sprawdzić jego stan bez nawet wywołanie funkcji. W przeciwieństwie do wzorca errno, kod błędu nie jest globalny, co sprawia, że wątek podejścia jest Bezpieczny, o ile każdy uchwyt jest prawidłowo używany.

Przykład:

MyHandle * h = MyApiCreateHandle();

/* first call checks for pointer nullity, since we cannot retrieve error code
   on a NULL pointer */
if (h == NULL)
     return 0; 

/* from here h is a valid handle */

/* get a pointer to the error struct that will be updated with each call */
MyApiError * err = MyApiGetError(h);

MyApiFileDescriptor * fd = MyApiOpenFile("/path/to/file.ext");

/* we want to know what can go wrong */
if (err->code != MyApi_ERROR_OK) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

MyApiRecord record;

/* here the API could refuse to execute the operation if the previous one
   yielded an error, and eventually close the file descriptor itself if
   the error is not recoverable */
MyApiReadFileRecord(h, &record, sizeof(record));

/* we want to know what can go wrong, here using a macro checking for failure */
if (MyApi_FAILED(err)) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}
 5
Author: SirDarius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-15 18:59:19

Pierwsze podejście jest lepsze IMHO:

  • łatwiej jest w ten sposób napisać funkcję. Gdy zauważysz błąd w środku funkcji, po prostu zwracasz wartość błędu. W drugim podejściu należy przypisać wartość błędu do jednego z parametrów, a następnie coś zwrócić.... ale co byś zwrócił - nie masz poprawnej wartości i nie zwracasz wartości błędu.
  • jest bardziej popularny, więc będzie łatwiejszy do zrozumienia, utrzymania
 4
Author: Klesk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:09:50

Zdecydowanie wolę pierwsze rozwiązanie:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

Lekko zmodyfikowałbym, aby:

int size;
MYAPIError rc;

rc = getObjectSize(h, &size)
if ( rc != MYAPI_SUCCESS) {
  // Error handling
}

W dodatku nigdy nie będę mieszać legalnej wartości zwracanej z błędem, nawet jeśli obecnie zakres funkcji pozwala na to, nigdy nie wiadomo, w którą stronę implementacja funkcji pójdzie w przyszłości.

I jeśli już mówimy o obsłudze błędów, sugerowałbym goto Error; jako kod obsługi błędów, chyba że można wywołać jakąś funkcję undo do obsługi błędów prawidłowo.

 4
Author: Ilya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 12:10:57

To, co możesz zrobić zamiast zwracać swój błąd, a tym samym zabraniać zwracania danych za pomocą funkcji, to użyć wrapper dla Twojego typu powrotu:

typedef struct {
    enum {SUCCESS, ERROR} status;
    union {
        int errCode;
        MyType value;
    } ret;
} MyTypeWrapper;

Następnie w wywołanej funkcji:

MyTypeWrapper MYAPIFunction(MYAPIHandle h) {
    MyTypeWrapper wrapper;
    // [...]
    // If there is an error somewhere:
    wrapper.status = ERROR;
    wrapper.ret.errCode = MY_ERROR_CODE;

    // Everything went well:
    wrapper.status = SUCCESS;
    wrapper.ret.value = myProcessedData;
    return wrapper;
} 

Zauważ, że w poniższej metodzie, wrapper będzie miał Rozmiar MyType plus jeden bajt (w większości kompilatorów), co jest dość opłacalne; i nie będziesz musiał naciskać kolejnego argumentu na stosie podczas wywoływania funkcji (returnedSize lub returnedError w obu zaprezentowanych przez Ciebie metodach).

 3
Author: Antoine C.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-08 21:20:13

Wpadłem na to pytanie kilka razy i chciałem udzielić bardziej wyczerpującej odpowiedzi. Myślę, że najlepszym sposobem na zastanowienie się nad tym jest jak zwracać błędy rozmówcy i co zwracasz.

Jak

Istnieją 3 sposoby zwracania informacji z funkcji:

  1. Wartość Zwracana
  2. Out Argument(S)
  3. poza pasmem, który obejmuje nielokalne goto (setjmp / longjmp), zmienne o zasięgu globalnym lub pliku, system plików itd.

Wartość Zwracana

Możesz zwrócić tylko jeden obiekt, jednak może to być dowolny kompleks. Oto przykład funkcji zwracającej błąd:

  enum error hold_my_beer();

Jedną z zalet zwracanych wartości jest to, że pozwala na łańcuchowanie wywołań w celu mniej inwazyjnej obsługi błędów:

  !hold_my_beer() &&
  !hold_my_cigarette() &&
  !hold_my_pants() ||
  abort();

Nie chodzi tylko o czytelność, ale może również pozwolić na przetwarzanie tablicy takich wskaźników funkcji w jednolity sposób.

Out Argument(s)

You can return więcej przez więcej niż jeden obiekt przez argumenty, ale najlepsza praktyka sugeruje, aby całkowita liczba argumentów była niska (powiedzmy,

void look_ma(enum error *e, char *what_broke);

enum error e;
look_ma(e);
if(e == FURNITURE) {
  reorder(what_broke);
} else if(e == SELF) {
  tell_doctor(what_broke);
}

Out of Band

Za pomocą setjmp () definiujesz miejsce i sposób, w jaki chcesz obsłużyć wartość int, i przenosisz kontrolę do tej lokalizacji za pomocą longjmp (). Zobacz praktyczne użycie setjmp i longjmp w C .

Co

  1. wskaźnik
  2. kod
  3. obiekt
  4. Callback

Wskaźnik

Wskaźnik błędu mówi tylko, że istnieje problem, ale nic o naturze tego problemu:

struct foo *f = foo_init();
if(!f) {
  /// handle the absence of foo
}

Jest to najmniej wydajny sposób komunikowania stanu błędu przez funkcję, jednak idealny, jeśli wywołujący nie może reagować na błąd w sposób stopniowany.

Kod

Kod błędu informuje dzwoniącego o naturze problemu i może pozwolić na odpowiednią odpowiedź(z powyższego). Może to być wartość zwracana lub jak w przykładzie look_ma() powyżej argument błędu.

Obiekt

Za pomocą obiektu error, wywołujący może być informowany o dowolnie skomplikowanych problemach. Na przykład kod błędu i odpowiedni komunikat czytelny dla człowieka. Może również poinformować rozmówcę, że wiele rzeczy poszło nie tak, lub błąd na element podczas przetwarzania kolekcji:

struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
   if(reason[i] == NOT_FOUND) find(friends[i]);
}

Zamiast wstępnie przydzielając tablicę błędów, możesz również (ponownie)przydzielić ją dynamicznie w razie potrzeby.

Callback

Callback jest najpotężniejszym sposobem radzenia sobie z błędami, ponieważ możesz powiedzieć funkcji, jakie zachowanie chcesz zobaczyć, gdy coś pójdzie nie tak. Argument wywołania zwrotnego może być dodany do każdej funkcji, lub jeśli customization uis wymagane tylko dla wystąpienia struktury jak ta:

 struct foo {
    ...
    void (error_handler)(char *);
 };

 void default_error_handler(char *message) { 
    assert(f);
    printf("%s", message);
 }

 void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
    assert(f);
    f->error_handler = eh;
 }

 struct foo *foo_init() {
    struct foo *f = malloc(sizeof(struct foo));
    foo_set_error_handler(f, default_error_handler);
    return f;
 }


 struct foo *f = foo_init();
 foo_something();

Jedną z ciekawych zalet wywołania zwrotnego jest to, że można go wywoływać wielokrotnie razy, lub wcale przy braku błędów, w których nie ma narzutu na szczęśliwą ścieżkę.

Istnieje jednak inwersja sterowania. Kod wywołujący nie wie, czy wywołanie zwrotne zostało wywołane. Jako takie, może mieć sens również użycie wskaźnika.

 3
Author: Allan Wind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-20 07:33:59

Oprócz tego, co zostało powiedziane, przed zwróceniem kodu błędu, uruchom assert lub podobną diagnostykę, gdy błąd zostanie zwrócony, ponieważ ułatwi to śledzenie. Sposób, w jaki to robię, to posiadanie spersonalizowanego assert, który nadal zostanie skompilowany w momencie wydania, ale zostanie zwolniony tylko wtedy, gdy oprogramowanie jest w trybie diagnostycznym, z opcją cichego raportowania do pliku dziennika lub pauzy na ekranie.

Osobiście zwracam kody błędów jako ujemne liczby całkowite z no_error jako zero , ale zostawia cię z możliwym następującym błędem

if (MyFunc())
 DoSomething();

Alternatywą jest mieć błąd zawsze zwracany jako zero i użyć funkcji LastError (), aby podać szczegóły rzeczywistego błędu.

 2
Author: SmacL,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 11:59:56

EDIT:jeśli potrzebujesz dostępu tylko do ostatniego błędu, a nie pracujesz w środowisku wielowątkowym.

Możesz zwracać tylko true / false (lub coś w rodzaju # define, jeśli pracujesz w C i nie obsługujesz zmiennych bool) i mieć globalny bufor błędów, który będzie zawierał ostatni błąd:

int getObjectSize(MYAPIHandle h, int* returnedSize);
MYAPI_ERROR LastError;
MYAPI_ERROR* getLastError() {return LastError;};
#define FUNC_SUCCESS 1
#define FUNC_FAIL 0

if(getObjectSize(h, &size) != FUNC_SUCCESS ) {
    MYAPI_ERROR* error = getLastError();
    // error handling
}
 1
Author: Igor Oks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 13:20:14

Drugie podejście pozwala kompilatorowi produkować bardziej zoptymalizowany kod, ponieważ gdy adres zmiennej jest przekazywany do funkcji, kompilator nie może zachować jej wartości w rejestrze(rejestrach) podczas kolejnych wywołań do innych funkcji. Kod zakończenia jest zwykle używany tylko raz, tuż po wywołaniu, podczas gdy" prawdziwe " dane zwracane z wywołania mogą być używane częściej

 1
Author: dmityugov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-22 15:40:15

Preferuję obsługę błędów w C przy użyciu następującej techniki:

struct lnode *insert(char *data, int len, struct lnode *list) {
    struct lnode *p, *q;
    uint8_t good;
    struct {
            uint8_t alloc_node : 1;
            uint8_t alloc_str : 1;
    } cleanup = { 0, 0 };

   // allocate node.
    p = (struct lnode *)malloc(sizeof(struct lnode));
    good = cleanup.alloc_node = (p != NULL);

   // good? then allocate str
    if (good) {
            p->str = (char *)malloc(sizeof(char)*len);
            good = cleanup.alloc_str = (p->str != NULL);
    }

   // good? copy data
    if(good) {
            memcpy ( p->str, data, len );
    }

   // still good? insert in list
    if(good) {
            if(NULL == list) {
                    p->next = NULL;
                    list = p;
            } else {
                    q = list;
                    while(q->next != NULL && good) {
                            // duplicate found--not good
                            good = (strcmp(q->str,p->str) != 0);
                            q = q->next;
                    }
                    if (good) {
                            p->next = q->next;
                            q->next = p;
                    }
            }
    }

   // not-good? cleanup.
    if(!good) {
            if(cleanup.alloc_str)   free(p->str);
            if(cleanup.alloc_node)  free(p);
    }

   // good? return list or else return NULL
    return (good ? list : NULL);
}

Źródło: http://blog.staila.com/?p=114

 1
Author: Nitin Kunal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-20 23:32:51

W dodatku inne świetne odpowiedzi, proponuję spróbować oddzielić flagę błędu i Kod błędu, aby zapisać jedną linię na każdym wywołaniu, tj.:

if( !doit(a, b, c, &errcode) )
{   (* handle *)
    (* thine  *)
    (* error  *)
}

Gdy masz dużo sprawdzania błędów, to małe uproszczenie naprawdę pomaga.

 1
Author: Ant_222,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-01 20:00:04

Jeśli chcesz, aby twój program się zawiesił i nie znał przyczyny, to zaufaj programistom i podstawowej obsłudze błędów C.

Myślę, że najlepiej jest zbudować jakiś rodzaj raportowania błędów, nazwać go trybem debugowania, wyłączyć go, gdy chcesz uzyskać najlepszą wydajność i włączyć, gdy chcesz debugować problem. Mam nadzieję, że uda Ci się trafić jeszcze raz.

Będą błędy, pytanie brzmi, jak chcesz spędzić dni i noce szukając ich.

 -2
Author: Adrian Gaile,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2021-01-10 13:56:13