C kodunda hata işleme


152

Bir C kütüphanesinde hataların tutarlı bir şekilde ele alınması söz konusu olduğunda "en iyi uygulama" nı düşünürsünüz.

Düşündüğüm iki yol var:

Her zaman hata kodunu döndürün. Tipik bir fonksiyon şöyle görünür:

MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);

Her zaman bir hata işaretçisi yaklaşımı sağlayın:

int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);

İlk yaklaşımı kullanırken, hata işleme kontrolünün doğrudan işlev çağrısına yerleştirildiği gibi bir kod yazmak mümkündür:

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

Buradaki hata işleme kodundan daha iyi görünüyor.

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

Ancak, veri döndürmek için döndürme değeri kullanarak kodu daha okunabilir hale düşünüyorum, ikinci örnekte boyut değişkenine bir şey yazılmış olduğu açıktır.

Neden bu yaklaşımlardan herhangi birini tercih etmem ya da belki de bunları karıştırmam ya da başka bir şey kullanmam gerektiğine dair bir fikriniz var mı? Global hata durumlarının hayranı değilim çünkü kütüphanenin çok iş parçacıklı kullanımını daha acı verici hale getiriyor.

EDIT: C ++ belirli fikirler şu anda benim için bir seçenek olmadığı için istisnalar dahil olmadığı sürece duymak ilginç olurdu ...


Ben sadece yaklaşık iki haftadır C öğreniyorum, ama bunun için aldığım duygu, OUT parametrelerinin işlevlerin çoğunluğu için defacto dönüş değeri olmasıdır, çünkü geri dönen yapıların değerlerine ek yükü önler ve hafifletir. çoğu değişken yığının üzerinde olduğu için belleği yeniden ayırma ihtiyacı. Bu nedenle, işlevin gerçek değeri için "return" kullanmıyorum, çoğu zaman hata işleme için kullanmakta özgürüm.
Joel Roberts

Yanıtlar:


74

Dönüş değeri yolu olarak hatayı seviyorum. API'yi tasarlıyorsanız ve kütüphanenizi mümkün olduğunca acısız bir şekilde kullanmak istiyorsanız, bu eklemeleri düşünün:

  • olası tüm hata durumlarını bir typedef'ed enum'da saklayın ve lib'inizde kullanın. Sadece ints veya daha da kötüsü, ints veya farklı numaralandırmalar dönüş kodları ile karıştırmayın.

  • hataları insan tarafından okunabilen bir şeye dönüştüren bir işlev sağlar. Basit olabilir. Sadece hata numaralandırma, const char * out.

  • Bu fikrin çok iş parçacıklı kullanımı biraz zorlaştırdığını biliyorum, ancak uygulama programcısı küresel bir hata geri arama ayarlayabilirse iyi olurdu. Bu şekilde, böcek avı oturumları sırasında geri aramaya bir kesme noktası koyabilecekler.

Umarım yardımcı olur.


5
Neden "bu fikir çok iş parçacıklı kullanımı biraz zorlaştırıyor" diyorsunuz. Çok parçacıklı işlemle hangi bölüm zorlaşıyor? Kısa bir örnek verebilir misiniz?
SayeedHussain

1
@crypticcoder Basitçe söylediniz: ne iş parçacığı bağlamında genel bir hata geri çağırılabilir. Sadece hatayı yazdırırsanız, herhangi bir sorunla karşılaşmazsınız. Sorunları düzeltmeye çalışırsanız, hangi çağrı dizisinin hataya neden olduğunu bulmanız gerekir ve bu da işleri zorlaştırır.
Nils Pipenbrinck

9
Hatanın daha fazla ayrıntısını iletmek isterseniz ne olur? Bir ayrıştırıcı hatası var ve sözdizimi hatasının satır numarasını ve sütununu ve hepsini güzel bir şekilde yazdırmanın bir yolunu sağlamak istiyorsunuz.
panzi

1
@panzi sonra açıkça bir yapı döndürmeniz (veya yapı gerçekten büyükse bir çıkış işaretçisi kullanmanız) ve yapıyı dize olarak biçimlendirecek bir işleve sahip olmanız gerekir.
Winger Sendon


92

Her iki yaklaşımı da kullandım ve ikisi de benim için iyi çalıştı. Hangisini kullanırsam kullanın, her zaman bu prensibi uygulamaya çalışırım:

Tek olası hatalar programcı hatalarıysa, bir hata kodu döndürmeyin, işlevin içindeki ekleri kullanın.

Girişleri doğrulayan bir iddia, işlevin ne beklediğini açıkça bildirirken, çok fazla hata denetimi program mantığını gizleyebilir. Tüm çeşitli hata durumları için ne yapılacağına karar vermek tasarımı gerçekten zorlaştırabilir. Programlayıcının asla bir tanesini geçememesi için ısrar ederseniz, neden functionX'in bir boş gösterici ile nasıl başa çıkacağını anlamalısınız?


1
C'deki örneklere bir örnek mi var? (C'ye karşı çok
yeşilim

assert(X)X'in doğru olmasını istediğiniz herhangi bir geçerli C ifadesi olduğu kadar basittir . bkz. stackoverflow.com/q/1571340/10396 .
Mart'ta

14
Ugh, kesinlikle kütüphane kodunda ekleri kullanmayın ! Ayrıca, başkalarının yaptığı gibi tek bir kod parçasında çeşitli hata işleme stillerini karıştırmayın
mirabilos

10
Stilleri karıştırmama konusunda kesinlikle katılıyorum. Eklerle ilgili nedeninizi merak ediyorum. İşlev belgelerim "X argümanı NULL olmamalı" veya "Y bu numaralandırmaya üye olmalı" diyorsa, assert(X!=NULL);veya assert(Y<enumtype_MAX);? Programcıların bu cevabına ve bunun neden doğru yol olduğunu düşündüğüm hakkında daha fazla ayrıntı için bağlantılandırdığı soruya bakın .
AShelly

8
@AShelly Sorun, genellikle sürümde orada olmadığını iddia ediyor.
Calmarius

29

CMU'nun CERT'inde, ortak C (ve C ++) hata işleme tekniklerinin her birinin ne zaman kullanılacağına dair öneriler içeren hoş bir slayt kümesi vardır . En iyi slaytlardan biri bu karar ağacı:

Karar Ağacı İşleme Hatası

Bu akış şemasıyla ilgili iki şeyi kişisel olarak değiştirirdim.

İlk olarak, bazen nesnelerin hataları belirtmek için dönüş değerlerini kullanması gerektiğini açıklığa kavuşturabilirim. Bir işlev yalnızca bir nesneden veri alır ancak nesneyi değiştirmezse, nesnenin kendisinin bütünlüğü risk altında değildir ve bir dönüş değeri kullanarak hataları göstermek daha uygundur.

İkincisi, C ++ 'da istisnalar kullanmak her zaman uygun değildir . İstisnalar iyidir, çünkü hata işlemeye ayrılan kaynak kodu miktarını azaltabilirler, çoğunlukla işlev imzalarını etkilemezler ve çağrı kaydından hangi verilerden geçebilecekleri konusunda çok esnektirler. Öte yandan, istisnalar birkaç nedenden dolayı doğru seçim olmayabilir:

  1. C ++ istisnalarının çok belirgin bir semantiği vardır. Bu anlambilimi istemiyorsanız, C ++ istisnaları kötü bir seçimdir. Bir istisna atıldıktan hemen sonra ele alınmalıdır ve tasarım, çağrı kaydının birkaç seviyeyi çözmesi için bir hatanın gerekeceği durumu tercih eder.

  2. İstisnaları atan C ++ işlevleri daha sonra istisnaları atmayacak şekilde sarılamaz, en azından istisnaların tam maliyetini ödemeden. Hata kodlarını döndüren işlevler, C ++ istisnalarını atmak için sarılabilir ve bu da onları daha esnek hale getirir. C ++ 'lar newbu hakkı fırlatmayan bir varyant sağlayarak alır.

  3. C ++ istisnaları nispeten pahalıdır, ancak bu dezavantaj, istisnaları mantıklı kullanan programlar için çoğunlukla abartılıdır. Bir program, performansın önemli olduğu bir kod yoluna istisnalar atmamalıdır. Programınızın bir hatayı ne kadar hızlı bildirip çıkabileceği gerçekten önemli değil.

  4. Bazen C ++ istisnaları bulunmaz. Ya bir kişinin C ++ uygulamasında mevcut değildir ya da kod kuralları onları yasaklar.


Orijinal soru çok evreli bir bağlam hakkında olduğu için, ben yerel hata göstergesi tekniği (anlatıldığı ne düşünüyorsunuz SirDarius 'ın cevabı ) orijinal cevapları underappreciated edildi. İş parçacığı güvenli, hatayı arayan tarafından hemen ele alınmaya zorlamaz ve hatayı açıklayan rastgele verileri paketleyebilir. Dezavantajı, bir nesne tarafından tutulmalıdır (veya bir şekilde harici olarak ilişkili olduğunu varsayalım) ve göz ardı edilebilir bir dönüş kodundan daha kolay.


5
Google'ın C ++ kodlama standartlarının hala C ++ istisnalarını kullanmadığımızı
Jonathan Leffler

19

Bir kütüphane oluşturduğumda ilk yaklaşımı kullanıyorum. Bir dönüş kodu olarak typedef'ed bir enum kullanmanın çeşitli avantajları vardır.

  • İşlev bir dizi gibi daha karmaşık bir çıktı döndürüyorsa ve uzunluğu döndürmek için rasgele yapılar oluşturmanıza gerek yoktur.

    rc = func(..., int **return_array, size_t *array_length);
  • Basit, standartlaştırılmış hata işlemeye izin verir.

    if ((rc = func(...)) != API_SUCCESS) {
       /* Error Handling */
    }
  • Kitaplık işlevinde basit hata işlemeye izin verir.

    /* Check for valid arguments */
    if (NULL == return_array || NULL == array_length)
        return API_INVALID_ARGS;
  • Bir typedef'ed enum kullanmak, enum adının hata ayıklayıcıda görünür olmasını sağlar. Bu, bir başlık dosyasına sürekli bakmaya gerek kalmadan daha kolay hata ayıklamaya olanak tanır. Bu numaralandırmayı bir dizeye çevirecek bir işleve sahip olmak da yardımcı olur.

Kullanılan yaklaşımdan bağımsız olarak en önemli konu tutarlı olmaktır. Bu, işlev ve bağımsız değişken adlandırma, bağımsız değişken sıralaması ve hata işleme için geçerlidir.


9

Setjmp kullanın .

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

2
İkinci kod bloğu, cevabın en üstünde yer alan Francesco Nidito'nun sayfasındaki kodun önceki bir versiyonuna dayanır . ETRYBu cevap yazılmış beri kod revize edilmiştir.
Jonathan Leffler

2
Setjmp korkunç bir hata işleme stratejisidir. Setjmp ve longjmp çağrıları arasında herhangi bir ayırırsanız, hataya eğilimli (geçici olmayan yerliler değişmiş değerlerini ve tümünü korumadıkları için w / eğilimli) ve kaynak sızdırıyor. Sigjmp / longjmp maliyetini telafi etmeden önce 30 iade ve int-val kontrolü yapabilmeniz gerekir. Çoğu çağrı deposu, özellikle özyinelemeye ağır gitmezseniz (ve eğer yaparsanız, iade + çeklerin maliyeti dışında mükemmel sorunlarınız varsa) bu kadar derine gitmez.
PSkocik

1
Eğer hafızayı yanlış yerleştirir ve sonra fırlatırsanız, hafıza sonsuza kadar sızar. Ayrıca setjmppahalı, hiç hata atılmasa bile biraz CPU zamanı ve yığın alanı tüketecek. Windows için gcc kullanırken, C ++ için farklı istisna işleme yöntemleri arasında seçim yapabilirsiniz, bunlardan biri temel alır setjmpve kodunuzu uygulamada% 30'a kadar yavaşlatır.
Mecki

7

Şahsen eski yaklaşımı tercih ediyorum (hata göstergesi döndürme).

Gerektiğinde, dönüş sonucu sadece bir hatanın oluştuğunu göstermelidir, tam hatayı bulmak için başka bir işlev kullanılır.

GetSize () örneğinizde, boyutların her zaman sıfır veya pozitif olması gerektiğini düşünürüm, bu nedenle negatif bir sonuç döndürmek, UNIX sistem çağrılarının yaptığı gibi bir hatayı gösterebilir.

İşaretçi olarak iletilen bir hata nesnesi ile ikinci yaklaşım için kullandığım herhangi bir kütüphane düşünemiyorum. stdio, vb tüm bir dönüş değeri ile gidin.


1
Kayıt için, ikinci yaklaşımı kullandığım bir kütüphane Maya programlama API'sıdır. Gerçi C yerine bir c ++ kütüphanesi. Hatalarını nasıl ele aldığı konusunda oldukça tutarsızdır ve bazen hata dönüş değeri olarak geçer ve diğer zamanlarda sonucu referans olarak geçer.
Laserallan

1
unutmayın strtod, tamam, son argüman sadece hataları göstermek için değil, aynı zamanda yapıyor.
quinmars

7

Program yazarken, başlatma sırasında genellikle hata işleme için bir iş parçacığını döndürürüm ve bir kilit de dahil olmak üzere hatalar için özel bir yapı başlatırım. Sonra, bir hata algıladığımda, dönüş değerleri aracılığıyla, kural dışı durumdan yapıya bilgi girer ve kural dışı durum işleme iş parçacığına bir SIGIO gönderirim, sonra yürütmeye devam edip edemeyeceğime bakarım. Eğer yapamazsam, istisna iş parçacığına programı zarif bir şekilde durduran bir SIGURG gönderirim.


7

Geri dönen hata kodu, C'de hata işleme için genel yaklaşımdır.

Ancak son zamanlarda giden hata işaretçisi yaklaşımını da denedik.

Dönüş değeri yaklaşımına göre bazı avantajları vardır:

  • Dönüş değerini daha anlamlı amaçlar için kullanabilirsiniz.

  • Bu hata parametresini yazmanız, hatayı ele almanızı veya yaymanızı hatırlatır. (Dönüş değerini kontrol etmeyi asla unutma fclose, değil mi?)

  • Bir hata işaretçisi kullanırsanız, fonksiyonları çağırırken onu aşağı aktarabilirsiniz. İşlevlerden herhangi biri ayarlanmışsa, değer kaybolmaz.

  • Hata değişkeninde bir veri kesme noktası ayarlayarak, hatanın ilk nerede oluştuğunu yakalayabilirsiniz. Koşullu bir kesme noktası ayarlayarak belirli hataları da yakalayabilirsiniz.

  • Tüm hataları ele alıp almadığınızı kontrol etmeyi otomatik hale getirir. Kod kuralı sizi hata işaretçinizi olarak çağırmaya zorlayabilir errve son bağımsız değişken olmalıdır. Böylece komut dosyası dizeyle eşleşebilir ve err);ardından gelip gelmediğini kontrol edebilir if (*err. Aslında pratikte CER(err dönüşünü kontrol et) ve CEG(err goto'yu kontrol et ) adında bir makro yaptık . Bu nedenle, yalnızca hataya geri dönmek istediğimizde her zaman yazmanıza gerek yoktur ve görsel karmaşayı azaltabilir.

Kodumuzdaki tüm işlevler bu giden parametreye sahip değildir. Bu giden parametre öğesi, normalde bir istisna atacağınız durumlar için kullanılır.


6

Geçmişte birçok C programlaması yaptım. Ve hata kodu dönüş değerini gerçekten takdir ettim. Ancak birkaç olası tuzağı vardır:

  • Yinelenen hata numaraları, bu global error.h dosyası ile çözülebilir.
  • Hata kodunu kontrol etmeyi unutmak, bu bir cluebat ve uzun hata ayıklama saatleri ile çözülmelidir. Ama sonunda öğreneceksiniz (ya da başka birinin hata ayıklamayı yapacağını bileceksiniz).

2
ikinci problem, uygun derleyici uyarı seviyesi, uygun kod inceleme mekanizması ve statik kod analiz araçları ile çözülebilir.
Ilya

1
Prensip üzerinde de çalışabilirsiniz: API işlevi çağrılırsa ve dönüş değeri kontrol edilmezse, bir hata vardır.
Jonathan Leffler

6

UNIX yaklaşımı en çok ikinci önerinize benzer. Sonucu veya tek bir "yanlış gitti" değerini döndürün. Örneğin, open dosya tanımlayıcıyı başarı durumunda veya -1 hatada döndürür. Başarısızlık da setleri bunun üzerine errno, harici küresel tamsayı göstermek için hangi hatanın meydana geldiğini .

Değeri için, Cocoa da benzer bir yaklaşım benimsiyor. Birkaç yöntem BOOL döndürür ve bir NSError **parametre alır, böylece hata durumunda hatayı ayarlar ve NO döndürürler. Sonra hata işleme şöyle görünür:

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

hangi iki seçenek arasında bir yerde :-).



5

Burada ilginç olduğunu düşündüğüm bir yaklaşım ve biraz disiplin gerektiriyor.

Bu, tanıtıcı türü bir değişkenin tüm API işlevlerini çalıştıran örnek olduğunu varsayar.

Fikir, tanıtıcı arkasındaki yapının önceki hatayı gerekli verilerle (kod, mesaj ...) bir yapı olarak depolaması ve kullanıcıya bu hata nesnesini bir işaretçi döndüren bir işlev sağlamasıdır. Her işlem sivri uçlu nesneyi güncelleyecek, böylece kullanıcı işlevleri çağırmadan bile durumunu kontrol edebilecektir. Errno modelinin aksine, hata kodu genel değildir, bu da her bir tutamaç düzgün kullanıldığı sürece yaklaşımı iş parçacığı açısından güvenli kılar.

Misal:

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

4

İlk yaklaşım daha iyi IMHO:

  • Fonksiyonu bu şekilde yazmak daha kolaydır. Fonksiyonun ortasında bir hata fark ederseniz, sadece bir hata değeri döndürürsünüz. İkinci yaklaşımda parametrelerden birine hata değeri atamanız ve sonra bir şey döndürmeniz gerekir .... ama ne döndürürsünüz - doğru değere sahip değilsiniz ve hata değerini döndürmezsiniz.
  • daha popüler, bu yüzden anlaşılması, bakımı daha kolay olacak

4

Kesinlikle ilk çözümü tercih ederim:

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

biraz değiştirmek, için:

int size;
MYAPIError rc;

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

Buna ek olarak, şu anda bunu yapmanıza izin veren işlev kapsamı olsa bile, meşru dönüş değerini asla hata ile karıştırmayacağım, gelecekte işlev uygulamasının hangi yolla gideceğini asla bilemezsiniz.

Hata işleme hakkında zaten konuşursak goto Error;, undohata işlemeyi doğru işlemek için bazı işlevler çağrılmadıkça hata işleme kodu olarak öneririm .


3

Hatanızı döndürmek ve böylece işlevinizle veri döndürmenizi engellemek yerine yapabilecekleriniz , dönüş türünüz için bir sarıcı kullanmaktır :

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

Ardından, çağrılan işlevde:

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

Aşağıdaki yöntemle, paketleyicinin oldukça karlı olan MyType artı bir bayt (çoğu derleyicide) boyutuna sahip olacağını unutmayın; ve işlevinizi çağırdığınızda ( returnedSizeveya returnedErrorsunduğunuz yöntemlerin her ikisinde de) yığına başka bir argüman göndermeniz gerekmez .


3

İşte Nils Pipenbrinck'in cevabının ilk 2 mermisini burada göstermek için basit bir program .

İlk 2 mermisi:

  • olası tüm hata durumlarını bir typedef'ed enum'da saklayın ve lib'inizde kullanın. Sadece ints veya daha da kötüsü, ints veya farklı numaralandırmalar dönüş kodları ile karıştırmayın.

  • hataları insan tarafından okunabilen bir şeye dönüştüren bir işlev sağlar. Basit olabilir. Sadece hata numaralandırma, const char * out.

Adında bir modül yazdığınızı varsayın mymodule. İlk olarak, mymodule.h dosyasında numaralandırma tabanlı hata kodlarınızı tanımlarsınız ve bu kodlara karşılık gelen bazı hata dizeleri yazarsınız. Burada char *sadece ilk numaralandırma tabanlı hata kodunuz 0 değeri varsa ve daha sonra sayıları işlemezseniz iyi çalışır C dizeleri ( ) bir dizi kullanıyorum . Boşluklu veya diğer başlangıç ​​değerleriyle hata kodu numaraları kullanırsanız, yalnızca eşlenmiş bir C-string dizisi kullanmaktan (aşağıda yaptığım gibi) bir switch ifadesi kullanan bir işlev kullanmaya veya if / else ifadeleri kullanmanız gerekir. enum hata kodlarından yazdırılabilir C dizelerine (göstermediğim) eşlemek için. Seçim senin.

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, numaralandırma hata kodlarından yazdırılabilir C dizelerine eşlemek için eşleme işlevimi içeriyor:

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, bazı işlevleri çağırmayı ve bunlardan bazı hata kodlarını yazdırmayı gösteren bir test programı içerir:

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

Çıktı:


Mymodule_func1 () = MYMODULE_ERROR_OK
hata kodundan mymodule_func2 () = MYMODULE_ERROR_INVARG
hata kodundan numara tabanlı hata kodlarının gösterilmesi () = MYMODULE_ERROR_INVARG hata kodum mymodule_func3 () = MYMODMLELE

Referanslar:

Bu kodu kendiniz buradan çalıştırabilirsiniz: https://onlinegdb.com/ByEbKLupS .


2

Söylenenlere ek olarak, hata kodunuzu iade etmeden önce, bir hata döndürüldüğünde bir iddiayı veya benzer bir teşhisi ateşleyin, çünkü izlemeyi çok daha kolay hale getirecektir. Bunu yapmanın yolu, sürümde hala derlenen ancak yalnızca yazılım tanılama modundayken, bir günlük dosyasına sessizce rapor vermek veya ekranda duraklatmak için bir seçenek olan özelleştirilmiş bir iddiaya sahip olmaktır.

Şahsen hata kodlarını sıfır olarak no_error ile negatif tamsayı olarak döndürüyorum , ancak olası aşağıdaki hata ile sizi bırakıyor

if (MyFunc())
 DoSomething();

Alternatif olarak, hata her zaman sıfır olarak döndürülür ve gerçek hatanın ayrıntılarını sağlamak için LastError () işlevi kullanılır.


2

Bu soru-cevap bölümüne birkaç kez rastladım ve daha kapsamlı bir cevap vermek istedim. Bu konuda düşünmek için en iyi yol olduğunu düşünüyorum nasıl arayana hataları dönün ve için neler iade.

Nasıl

Bir işlevden bilgi döndürmenin 3 yolu vardır:

  1. Geri dönüş değeri
  2. Bağımsız Değişken (ler)
  3. Bant Dışı, yerel olmayan goto (setjmp / longjmp), dosya veya genel kapsamlandırılmış değişkenler, dosya sistemi vb.

Geri dönüş değeri

Değeri yalnızca tek bir nesne olarak döndürebilirsiniz, ancak rastgele bir kompleks olabilir. Aşağıda hata döndürme işlevi örneği verilmiştir:

  enum error hold_my_beer();

Dönüş değerlerinin bir yararı, daha az müdahaleci hata işleme için çağrıların zincirlenmesine izin vermesidir:

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

Bu sadece okunabilirlikle ilgili değil, aynı zamanda bu tür fonksiyon işaretleyicilerinin bir dizisinin muntazam bir şekilde işlenmesine de izin verebilir.

Bağımsız Değişken (ler)

Bağımsız değişkenler aracılığıyla birden fazla nesne üzerinden daha fazla geri dönebilirsiniz, ancak en iyi uygulama toplam bağımsız değişken sayısını düşük tutmanızı önerir (örneğin, <= 4):

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

Bant dışı

Setjmp () ile bir yer ve int değerini nasıl işlemek istediğinizi tanımlarsınız ve kontrolü bir longjmp () aracılığıyla o konuma aktarırsınız. Bkz . C'de setjmp ve longjmp'nin pratik kullanımı .

Ne

  1. Gösterge
  2. kod
  3. Nesne
  4. Geri aramak

Gösterge

Bir hata göstergesi sadece bir sorun olduğunu söyler, ancak söz konusu sorunun doğası hakkında hiçbir şey yoktur:

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

Bu, bir işlevin hata durumunu bildirmek için en az güçlü yoldur, ancak arayan hataya yine de dereceli bir şekilde yanıt veremezse mükemmeldir.

kod

Bir hata kodu arayan kişiye sorunun niteliği hakkında bilgi verir ve uygun bir yanıta (yukarıdan) izin verebilir. Dönüş değeri veya bir hata bağımsız değişkeninin üzerindeki look_ma () örneği gibi olabilir.

Nesne

Bir hata nesnesiyle, arayan rastgele karmaşık sorunlar hakkında bilgilendirilebilir. Örneğin, bir hata kodu ve uygun bir insan tarafından okunabilir mesaj. Arayan kişiye, birden çok şeyin yanlış gittiğini veya bir koleksiyonu işlerken öğe başına bir hata olduğunu bildirebilir:

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

Hata dizisini önceden ayırmak yerine, elbette gerektiği gibi dinamik olarak da yeniden atayabilirsiniz.

Geri aramak

Geri arama, hataları işlemenin en güçlü yoludur, çünkü işleve bir şeyler ters gittiğinde görmek istediğiniz davranışları söyleyebilirsiniz. Her işleve bir geri çağrı argümanı eklenebilir veya yalnızca aşağıdaki gibi bir yapının örneği başına özelleştirme gerekiyorsa:

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

Bir geri aramanın ilginç bir yararı, birden çok kez çağrılabilmesidir ya da mutlu yolda herhangi bir ek yükün bulunmadığı hataların yokluğunda hiç bir şekilde çağrılamaz.

Bununla birlikte, kontrolün ters çevrilmesi söz konusudur. Arama kodu geri aramanın başlatılıp başlatılmadığını bilmiyor. Bu nedenle, bir gösterge kullanmak da mantıklı olabilir.


1

DÜZENLE: Yalnızca son hataya erişmeniz gerekiyorsa ve çok iş parçacıklı bir ortamda çalışmıyorsanız.

Yalnızca true / false (veya C'de çalışıyorsanız ve bool değişkenlerini desteklemiyorsanız #define bir tür) döndürebilir ve son hatayı tutacak genel bir Hata arabelleğine sahip olabilirsiniz:

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
}

Gerçekten de C değil, OS tarafından sağlanmış olabilir ya da olmayabilir. Örneğin, gerçek zamanlı işletim sistemleri üzerinde çalışıyorsanız, buna sahip olmayacaksınız.
Ilya

1

İkinci yaklaşım, derleyicinin daha optimize edilmiş kod üretmesini sağlar, çünkü bir değişkenin adresi bir işleve iletildiğinde, derleyici diğer işlevlere sonraki çağrılar sırasında değerini kayıtlarda tutamaz. Tamamlama kodu genellikle çağrıdan hemen sonra yalnızca bir kez kullanılırken, çağrıdan döndürülen "gerçek" veriler daha sık kullanılabilir


1

Aşağıdaki tekniği kullanarak C hata işleme tercih ederim:

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

Kaynak: http://blog.staila.com/?p=114


1
İyi teknik. Ben bile gotoyerine 's' ile neater buluyorum if. Kaynaklar: bir , iki .
Ant_222

0

Buna ek olarak, diğer harika cevaplar, her çağrıda bir hat kaydetmek için hata bayrağını ve hata kodunu ayırmaya çalışmanızı öneririm, yani:

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

Çok fazla hata kontrolüne sahip olduğunuzda, bu küçük basitleştirme gerçekten yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.