2009-08-02 7 views
13

Ich komme aus einem C# -Hintergrund, aber ich lerne gerade C. Wenn Sie in C# signalisieren möchten, dass ein Fehler aufgetreten ist, lösen Sie eine Ausnahme aus. Aber was machst du in C?C: Wie simulieren Sie eine "Ausnahme"?

Sagen Sie zum Beispiel, Sie haben einen Stapel mit push und pop Funktionen. Was ist der beste Weg, um zu signalisieren, dass der Stapel während einer pop leer ist? Was kommst du von dieser Funktion zurück?

double pop(void) 
{ 
    if(sp > 0) 
     return val[--sp]; 
    else { 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 
} 

K&R ‚s Beispiel von Seite 77 (Code oben) gibt eine 0.0. Aber was ist, wenn der Benutzer zuvor eine 0.0 auf den Stapel geschoben hat, woher wissen Sie, ob der Stapel leer ist oder ob ein korrekter Wert zurückgegeben wurde?

Antwort

15

Exception-ähnliches Verhalten in C wird über setjmp/longjmp erreicht. Was Sie jedoch wirklich wollen, ist ein Fehlercode. Sind alle Werte möglicherweise zurückgenommen werden, dann können Sie in einem Out-Parameter als Zeiger zu nehmen, und zu verwenden, die den Wert zurückzubringen, etwa so:

int pop(double* outval) 
{ 
     if(outval == 0) return -1; 
     if(sp > 0) 
       *outval = val[--sp]; 
     else { 
       printf("error: stack empty\n"); 
       return -1; 
     } 
     return 0; 
} 

Nicht ideal, natürlich, aber so die Grenzen sind von C.

Auch wenn Sie diese Straße gehen, möchten Sie möglicherweise symbolische Konstanten für Ihre Fehlercodes definieren (oder einige von the standard ones verwenden), damit ein Benutzer zwischen "Stapel leer" und "Sie gab mir unterscheiden kann ein Nullzeiger, Dummkopf ".

+2

Ich stimme dem nicht zu, denn selbst wenn ich verstehe, was du meinst, würde ich niemandem, der von java/C# land kommt, die Annahme geben, dass setjmp/longjmp in irgendeiner Weise die 'Lösung' für 'wo ist meine Ausnahme?' – Jonke

+0

Beachten Sie, dass sehr wenig C-Code aus historischen Gründen setjmp/longjmp verwendet. Fehlercodes sind die übliche C-Art. Fehlercodes sind nicht sehr gut; Das Fehlen von Ausnahmen in C ist ein Hauptgrund, warum modernere Sprachen besser sind. – Nelson

+0

ANSI hat 'setjmp' so codiert, dass es garantiert funktioniert (zumindest wenn der Compiler dem Standard entspricht), aber Stack Switch und Parallelität nicht standardisiert sind, so ist es immer noch ein Problem, ein sicheres Threads Paket zu schreiben für C (sogar mit asm für den Kontextwechsel), weil ein Compiler * könnte * (obwohl es unwahrscheinlich sein könnte) Optimierungen durchführen und transformiert , die Annahmen im Thread-Paket zu brechen. – Jonke

4

Ein Ansatz besteht darin, anzugeben, dass pop() undefiniertes Verhalten hat, wenn der Stapel leer ist. Sie müssen dann eine is_empty() -Funktion bereitstellen, die aufgerufen werden kann, um den Stack zu überprüfen.

Ein weiterer Ansatz ist C++ zu verwenden, die Ausnahmen hat :-)

+0

Mehr nutzbringend für diesen speziellen Fall, C++ einen Stapel direkt in der Bibliothek :-) –

+0

hat aber den C++ std :: Stapel pop Funktion tut eigentlich nicht, was das OP will. –

+1

Wahr, und zu verstehen, warum zu der C++ - Ausbildung des OPs, die der Hauptzweck der Frage ist, hinzufügen :-) Wie auch immer, jeweils einen Aufruf von 'top()' und 'pop()', eine Kopie zurückgeben, gibt die Das gleiche Endergebnis wie das, was das OP hat, und die Anwendung dessen, was Sie über die Notwendigkeit einer 'empty()' -Funktion sagen. IYSWIM. –

1

können Sie einen Zeiger auf Doppel zurück:

  • Nicht-NULL -> gültigen
  • NULL -> ungültig
+0

downvotes ohne Kommentare sind sinnlos, bitte erklären Sie Ihre downvotes – dfa

+3

Ich bin nicht der Downvote, aber ich würde mich fragen, woher der Sicherungsspeicher für den Zeiger kommt. Wenn es das popping-Element ist, muss der Aufrufer es dereferenzieren, bevor ein neuer Wert ausgegeben werden kann. Wenn es sich um ein separates 'statisches Double' handelt, muss der Aufrufer vor dem nächsten Popup-Aufruf dereferenzieren. Beides verursacht eine Menge Ärger für den Anrufcode. – RBerteig

+0

Ich habe auch nicht abgelehnt, aber ich hatte die gleichen Bedenken. Ich denke, es ist ein effektiver Ansatz, aber Sie müssen ändern, wie die Funktion funktioniert und Daten speichert, um es zu tun. – Jon

7

Sie haben ein paar Optionen:

1) Magie Fehlerwert. Nicht immer gut genug, aus dem Grund, den du beschreibst. Ich denke in der Theorie für diesen Fall könnten Sie ein NaN zurückgeben, aber ich empfehle es nicht.

2) Legen Sie fest, dass das Popup nicht zulässig ist, wenn der Stapel leer ist. Dann geht Ihr Code entweder davon aus, dass er nicht leer ist (und wird nicht definiert, wenn dies der Fall ist) oder behauptet.

3) Ändern Sie die Signatur der Funktion, so dass Sie Erfolg oder Misserfolg zeigen können.

int pop(double *dptr) 
{ 
    if(sp > 0) { 
      *dptr = val[--sp]; 
      return 0; 
    } else { 
      return 1; 
    } 
} 

Dokument als „Bei Erfolg 0 zurück und schreibt den Wert an der Stelle, auf die dptr On Fehler, gibt einen Wert ungleich Null zurück. "

Optional können Sie den Rückgabewert oder errno verwenden, um den Grund für einen Fehler anzugeben, obwohl es für dieses spezielle Beispiel nur einen Grund gibt.

4) Übergeben Sie ein "Ausnahme" -Objekt per Zeiger in jede Funktion und schreiben Sie bei einem Fehler einen Wert darauf. Der Anrufer prüft sie dann oder nicht, je nachdem, wie sie den Rückgabewert verwenden. Das ist ähnlich wie "errno", aber ohne dass es ein fadenweiter Wert ist.

5) Wie andere bereits gesagt haben, implementieren Sie Ausnahmen mit setjmp/longjmp. Es ist machbar, erfordert aber entweder die Übergabe eines zusätzlichen Parameters (das Ziel des longjmp, das bei einem Fehler ausgeführt werden soll) oder das Ausblenden in globals. Es macht auch die typische Ressourcenhandhabung im C-Stil zu einem Albtraum, denn man kann nichts nennen, was über die Stack-Ebene hinausspringen könnte, wenn man eine Ressource hält, für die man verantwortlich ist.

+0

+1: Für Punkt # 3. –

10

Sie könnten ein Ausnahmesystem über longjmp/setjmp erstellen: Exceptions in C with Longjmp and Setjmp. Es funktioniert tatsächlich ziemlich gut, und der Artikel ist auch eine gute Lektüre. Hier ist, wie Ihr Code aussehen könnte, wenn Sie das Ausnahme-System aus dem verknüpften Artikel verwendet:

TRY { 
    ... 
    THROW(MY_EXCEPTION); 
    /* Unreachable */ 
    } CATCH(MY_EXCEPTION) { 
    ... 
    } CATCH(OTHER_EXCEPTION) { 
    ... 
    } FINALLY { 
    ... 
    } 

Es ist erstaunlich, was man mit einem kleinen Makros tun kann, nicht wahr? Es ist ebenso erstaunlich, wie schwer es ist, herauszufinden, was zum Teufel passiert, wenn Sie nicht bereits wissen, was die Makros tun.

longjmp/setjmp sind tragbar: C89, C99 und POSIX.1-2001 geben setjmp() an.

Beachten Sie jedoch, dass Ausnahmen, die auf diese Weise implementiert werden, immer noch einige Einschränkungen im Vergleich zu "echten" Ausnahmen in C# oder C++ haben. Ein großes Problem ist, dass nur Ihr Code mit diesem Ausnahmesystem kompatibel ist. Da es keinen etablierten Standard für Ausnahmen in C gibt, werden Bibliotheken von Systemen und Drittanbietern nicht optimal mit Ihrem selbst entwickelten Ausnahmesystem zusammenarbeiten. Dennoch kann sich dies manchmal als hilfreich erweisen.

Ich empfehle nicht, dies in seriösen Code zu verwenden, mit denen andere Programmierer arbeiten sollten. Es ist einfach zu leicht, sich damit in den Fuß zu schießen, wenn man nicht genau weiß, was vor sich geht. Threading, Ressourcenverwaltung und Signalverarbeitung sind Problembereiche, auf die Nicht-Spielzeug-Programme stoßen werden, wenn Sie versuchen, longjmp-Ausnahmen zu verwenden.

+4

Ich habe so etwas in C++ erstellt, bevor Ausnahmen weit verbreitet sind. Ich habe sogar durch eigene Form des Stackabwickelns umgesetzt. Zum Glück kam ich zu Verstand, bevor wir es im Produktionscode verwendeten. –

+0

@Neil: Ich mochte den zweiten Teil des Satzes :-) – jeroenh

+1

"Zum Glück bin ich zu Verstand gekommen". Herzliche Glückwünsche. Symbian hat das Gleiche gemacht wie du, bis zu dem Punkt, an dem du zu deinen Sinnen gekommen bist und sie ausgeliefert haben. 10+ Jahre später haben sie immer noch NewLC überall ... –

2

Es gibt keine Entsprechung zu Ausnahmen in geraden C. Sie müssen Ihre Funktionssignatur entwerfen, um Fehlerinformationen zurückzugeben, wenn Sie das möchten.

Die Mechanismen in C sind:

  • Non-local GOTOs mit setjmp/longjmp
  • Signale

jedoch keine von ihnen hat fern Semantik C# ähnelt (oder C++) Ausnahmen .

3

In Fällen wie diesem, was Sie tun in der Regel eine von

  • dem Anrufer Lassen Sie es. z.B. Es liegt an dem Anrufer zu wissen, ob es sicher ist zu knallen() (z. B. eine stack-> is_empty() - Funktion aufzurufen, bevor der Stapel geöffnet wird), und wenn der Anrufer versagt, ist es seine Schuld und viel Glück.
  • Signalisieren Sie den Fehler über einen out-Parameter oder Rückgabewert.

z.B.Sie entweder nicht

double pop(int *error) 
{ 
    if(sp > 0) { 
     return val[--sp]; 
     *error = 0; 
    } else { 
    *error = 1; 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 

}

oder

int pop(double *d) 
{ 
    if(sp > 0) { 
     *d = val[--sp]; 
     return 0; 
    } else { 
    return 1; 

    } 
} 
0

Es gibt bereits einige gute Antworten hier, wollte nur erwähnen, dass etwas in der Nähe „Ausnahme“, kann mit dem Einsatz von getan werden ein Makro, wie in der ehrfürchtigen MinUnit getan (dies bringt nur die "Ausnahme" auf die Anruferfunktion).

1

1) Sie geben einen Flag-Wert zurück, um anzuzeigen, dass es fehlgeschlagen ist, oder Sie verwenden eine TryGet-Syntax, bei der die Rückgabe ein Boolescher Erfolg ist, während der Wert über einen Ausgabeparameter übergeben wird.

2) Wenn dies unter Windows ist, gibt es eine reine C-Form von Ausnahmen auf Betriebssystemebene, die Strust Exception Handling genannt wird und Syntax wie "_try" verwendet. Ich erwähne es, aber ich empfehle es nicht für diesen Fall.

4

Dies ist tatsächlich ein perfektes Beispiel für die Übel, die versuchen, den Rückgabetyp mit magischen Werten und einfach nur fragwürdigen Interface-Design zu überladen.

Eine Lösung könnte ich die Mehrdeutigkeit (und damit die Notwendigkeit einer „Ausnahme Verhalten“) in dem Beispiel zu beseitigen ist, einen richtigen Rückgabetyp zu definieren:

struct stack{ 
    double* pData; 
    uint32 size; 
}; 

struct popRC{ 
    double value; 
    uint32 size_before_pop; 
}; 

popRC pop(struct stack* pS){ 
    popRC rc; 
    rc.size=pS->size; 
    if(rc.size){ 
     --pS->size; 
     rc.value=pS->pData[pS->size]; 
    } 
    return rc; 
} 

Verwendung natürlich ist:

popRC rc = pop(&stack); 
if(rc.size_before_pop!=0){ 
    ....use rc.value 

Dies geschieht die ganze Zeit, aber in C++ auf solche Mehrdeutigkeiten zu vermeiden man in der Regel nur gibt ein

std::pair<something,bool> 

wo der Bool ein Erfolgsindikator ist - Blick auf einige:

std::set<...>::insert 
std::map<...>::insert 

Alternativ eine double* zur Schnittstelle hinzufügen und geben einen (! N UNOVERLOADED) Return-Code, sagen ein Enum Erfolg angibt.

Natürlich musste man die Größe in Struct popRC nicht zurückgeben. Es hätte sein können

Aber da Größe als nützlicher Hinweis zum pop'er dienen könnte, könnten Sie es auch verwenden.

BTW, ich stimme von ganzem Herzen, dass die Struktur-Stack-Schnittstelle

int empty(struct stack* pS){ 
    return (pS->size == 0) ? 1 : 0; 
} 
0

setjmp, longjmp und Makros haben sollte. Es ist eine beliebige Anzahl von Malen — getan worden die älteste Implementierung, die ich kenne, ist von Eric Roberts und Mark vanderVoorde —, aber die, die ich derzeit verwende, ist Teil von Dave Hansons C Interfaces and Implementations und ist frei von Princeton.

1

Etwas, das noch niemand erwähnt hat, ist es ziemlich obwohl hässlich:

int ok=0; 

do 
{ 
    /* Do stuff here */ 

    /* If there is an error */ 
    break; 

    /* If we got to the end without an error */ 
    ok=1; 

} while(0); 

if (ok == 0) 
{ 
    printf("Fail.\n"); 
} 
else 
{ 
    printf("Ok.\n"); 
}