Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Funktion bei Fehler verlassen

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
28.06.2003, 12:27 Uhr
BeS



Hallo,
wenn ich eine Funktion verlassen will wenn ein Fehler auftritt und diesen Fehler dann im Hauptprogramm (oder der funktion von der die andere funktion aufgerufen wurde) auswerten und darauf reagieren will, wie mache ich das dann am besten?
Wenn der Rückgabewert der funktion int ist könnte man die Fehler von 1..n durchnummerieren, daß könnte aber auch zu Fehlern führen wenn der berechnete Wert einem Fehlercode entspricht und wenn der Rückgabewert z.B. ein pointer ist geht diese Methode sowieso nichtmehr.
Eine andere Möglichkeit wäre exit(), dann wird aber gleich das ganze Programm beendet.

Wie kann ich am besten Fehler von einer funktion zurückgeben und dann darauf reagieren?

Danke!
--
If art interprets our dreams, the computer execute them in the guise of programs!
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
28.06.2003, 14:27 Uhr
virtual
Sexiest Bit alive
(Operator)


Nehmen wir mal als ganz einfaches Beispiel eine Funktion die eine GanzzahlAddition macht bei auch noch auf Überläufe geachtet wird:

C++:
unsigned add (unsigned a, unsigned b)
{
    if (a>UINT_MAX-b)
    {
        /* Fehlerfall */
        return 0;
     }else
     {
        return a+ b;
     }
}


Ich denke, so oder so ähnlich sieht Dein Problem aus: Die Funktion gibt 0 im Fehlerfall zurück, aber eben auch 0, wenn a und b beide 0 sind. Die Aufrufende Funktion wäre dann etwa so zu implementieren:

C++:
unsigned a, b, c;
a = ???;
b = ???;
c = add(a, b);
if (0==c && (0!=a || 0!=b))
{
     /* Fehler */
}


Ist natürlich ziemlich blöd. In unserem Beispiel mag man ja noch relativ einfach anhand der Eingabeparameter schließen können, ob da ein Fehler vorlag. Aber erstens ist obiges Verhalten sehr unbequem, zweitens ist es nicht immer möglich, aus den Eingabeparametern abzuleiten, ob der Ausgabewert einen Fehler repräsentiert oder nicht.

In C kann man zB folgendes machen: man führt ein Errorflag ein, welches wahlweise eine globale Variable ist oder mit der Funktion mitgeführt wird. Alle, die jetzt sagen: "Igitt - Globale Variable!" seien jetzt daran erinnert, daß es mit errno eben ein solche gibt, die Fehlerzustände speichert. Also der Weg mit der globalen Variable könnte so aussehen:

C++:
unsigned add (unsigned a, unsigned b)
{
    if (a>UINT_MAX-b)
    {
        errno = EINVAL;
        return 0;
     }else
     {
        errno = 0;
        return a+ b;
     }
}
....
unsigned a, b, c;
a = ???;
b = ???;
c = add(a, b);
if (errno)
{
     /* Fehler */
}


Noch immer nicht ganz so schön. Ich würde es auch nicht unbedingt empfehlen. Man kann den Errorindikator auch als zusätzlichen parameter übergeben:

C++:
unsigned add (unsigned a, unsigned b, int* error)
{
    if (a>UINT_MAX-b)
    {
        *error = 1;
        return 0;
     }else
     {
        *error = 0;
        return a+ b;
     }
}
....
unsigned a, b, c;
a = ???;
b = ???;
int e;
c = add(a, b, &e);
if (e)
{
     /* Fehler */
}


Bzw. so:

C++:
int add (unsigned a, unsigned b, unsigned* result)
{
    if (a>UINT_MAX-b)
    {
        return 1;
     }else
     {
        *result = a+ b;
        return 0;
     }
}
....
unsigned a, b, c;
a = ???;
b = ???;

if (add(a, b, &c))
{
     /* Fehler */
}


In C++ würde man Excaptions verwenden:

C++:
unsigned add(unsigned a, unsigned b)
{
     if (a>UINT_MAX-b) throw "Overflow";
     return a+b;
}

unsigned a, b, c;
a = ???;
b = ???;

try
{
    c = add(a,b);
   ....
}
catch(const char* msg)
{
    std::cerr<<"Error: "<<msg<<std::endl;
}



Hoffe zur Verwirrung beigetragen zu haben

Ich empfehle sobald Du in der C++ Welt bist (und mit solchen Problemen zu kämpfen hast) Exceptions, wobei das ein weites Feld ist: Du kannst jedes beliebige Objekt throwen (dh als Exception werfen) und entsprechend nahezu beliebeige Informationen zum Fehler mitgeben.
--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 28.06.2003 um 14:30 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
28.06.2003, 15:24 Uhr
BeS



Hallo virtual,
danke für die ausführliche Antwort!
Das Konzept mit den exceptions kenne ich von Ada95, ist schon eine tolle Sache, man wirft irgendwann eine exception und kann sie dann wieder fangen und verarbeiten.
Daraus entstand auch meine Frage, wie ich sowas wohl am besten in C realisieren könnte.
Du hast mir ja jetzt ein paar Möglichkeiten gezeigt

Danke!
--
If art interprets our dreams, the computer execute them in the guise of programs!
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
30.06.2003, 11:54 Uhr
ao

(Operator)


Es gibt verschiedene "Arten" von Funktionen, und es gibt verschiedene Modelle, wie man ein Ergebnis transportiert:

1. Gruppe: Funktionen, deren Hauptaufgabe es ist, ein Ergebnis zu ermitteln und zurückzugeben. Diese Gruppe teile ich noch in zwei Untergruppen:

1.1. Funktionen, die immer ein sinnvolles Ergebnis haben, z.B.

C++:
int Add (int a, int b); // zwei ganze Zahlen addieren; Ueberlauf lassen wir mal weg.


1.2. Funktionen die auch fehlschlagen können z.B.

C++:
float log2 (float x); // Zweierlogarithmus, nur fuer x > 0 definiert.

Hierzu gehören auch die Funktionen, die Objekte erzeugen (also intern malloc oder new machen), denn das kann auch fehlschlagen.

2. Gruppe: Funktionen, deren Hauptaufgabe es ist, eine Aktion durchzuführen, z.B. Daten irgendwo zu speichern oder zu übertragen. Die Rückgabe eines Statuswerts, der Erfolg oder Misserfolg anzeigt, kann (je nach Zusammenhang) wichtig oder unwichtig sein.

Die Funktionen aus der Gruppe 1.1 würde ich tatsächlich so definieren wie im Beispiel. Hier gibts keinen Grund, sich die Sache kompliziert zu machen.

Bei der Gruppe 1.2 gibt es zwei Ansätze:

A. so wie oben; Das funktioniert allerdings nur, wenn es sogenannte "Magic Values" gibt, mit denen ein Fehlschlagen angezeigt werden kann. Die log-Funktionen der C-Runtime-Library geben in diesem Falle z.B. NaN (Not a Number) zurück. Funktionen wie malloc erledigen das mit NULL.

Das Problem ist, daß die Funktion für den Benutzer ganz einfach aussieht (Argumente reinstecken, Ergebnis abholen und fertig). Erst auf den zweiten Blick merkt man, daß man das Ergebnis vor der Verwendung auf Gültigkeit testen muß; es sei denn, man weiß, daß das Ergebnis gültig sein wird, weil die Argumente gültig waren, aber auch das muß man sicherstellen (oder aus dem Kontext wissen).

Im allgemeinen Fall ist also der Vorteil für den Anwender (bequem aufrufbare Funktion, Ergebnis sofort verwendbar) entfallen; es muß zwischengespeichert und auf Magic Value getestet werden. Und (schlimmer): Was Magic Values sind und was sie bedeuten, ist von Funktion zu Funktion verschieden.

B. Eine Alternative:

C++:
float log2 (float x, int *pnStatus);

Es gelte die Vereinbarung, daß die Gültigkeit des Ergebnisses in der Variablen *pnStatus abgefragt werden kann. Falls das nicht nötig ist, weil der Anwender weiß, daß es gültig sein wird, kann er NULL hereinreichen; die Funktion log2 prüft das und legt in diesem Fall in *pnStatus keinen Statuswert ab.

Das heißt, der Anwender kann, muß aber nicht, den Statuswert abholen und prüfen. Wenn er ihn nicht prüft, ist er von dem Overhead befreit, eine Variable für den Statuswert zu definieren und auszuwerten, und die Funktion ist genauso einfach zu handhaben wie unter A.

Wenn er ihn prüfen will, bekommt er ihn in einem Format, das projektweit oder sogar unternehmensweit standardisiert werden kann. Es kann z.B. festgelegt werden, daß Status == 0 Success bedeutet und Status == 1 OutOfMemory, Status == 2 DivisionByZero, Status == 3 FileNotFound oder was auch immer. Es ist nicht mehr von der Semantik der Funktion abhängig, was gültige Werte sind und was Magic Values, die irgendeinen Fehler anzeigen. D.h. die Auswertung des Statuswerts kann durch Makros o.ä. weitgehend automatisiert werden. Dadurch entfallen viel lästige Tipparbeit und vor allem Fehlerquellen.

Funktionen der Gruppe 2 (Aktion ausführen) würde ich so definieren:

C++:
int DoSomething ( /* hier alle Ein- und Ausgabeparameter */);

wobei der Rückgabewert genau so ein Statuswert ist wie oben. Der Anwender kann sich hier aussuchen, ob er ihn auswertet oder in seinem Programm einfach ignoriert.

Übrigens würde ich den Statuswert nicht als bool zurückgeben, sondern tatsächlich als int. bool sind für mich nur die echten Bool-sche Werte, also Ergebnisse von logischen Ausdrücken (Vergleichen usw.). Statuswerte, die Erfolg oder Misserfolg anzeigen, sind für mich nicht Bool-sch.

Noch eins zu Exceptions: Mach nicht den Fehler, Exceptions als die billige Lösung aller Probleme anzusehen und wild mit irgendwelchen Exceptions um dich zu werfen. Exceptions sind im Kern nichts anderes als eine nette Verklausulierung von "goto NixWieRausHier;". Der Exception-Werfer muß sich Gedanken machen über den Zustand, in dem er sein Objekt verläßt; der Exception-Fänger muß auf alles gefaßt sein. Ein schlechtes Exception-Design kann ein Objekt praktisch unbrauchbar machen.

ao
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: