C++ Verhalten einer double Funktion im Fehlerfall testen

Seppel08

Ensign
Registriert
Okt. 2008
Beiträge
166
Hallo zusammen,

bin gerade dabei, einen Failure Test einer Funktion zu schreiben, die einen double Wert zurückgeben soll:
Code:
double Vektor::get(int index) const {

	// Überprüfung auf existierenden Index
	if ((index < mSize) && (index >= 0)) return mWerte[index]; // Wert zurückgeben

	// Fehlermeldung
	else {
		cerr << "ERROR: index does not exist." << endl;
	}
}
Bin mir nicht sicher, wie genau ich überprüfen soll, ob die Funktion im Fehlerfall (nicht existierender Index) das richtige macht. Momentan gibt die Funktion im Fehlerfall nichts zurück, was dann <-1.#IND> ergibt (was auch immer das sein soll). Darauf kann ich aber nicht testen. Ebenso wüsste ich nicht, wie ich auf Ausgeben der Fehlermeldung testen sollte. Einen double kann ich im Fehlerfall ja nicht zurückgeben, da jeder double als sinnvoller Wert interpretiert werden könnte.

Kann mir da jemand weiterhelfen?

Grüße

Seppel
 
Normalerweise verwendet man in solchen Fällen Exceptions. :) Dadurch kannst du an der Aufrufstelle in einem try-catch-Block prüfen, ob die Funktion eine Exception wirft und dann entsprechend handeln.
 
Zuletzt bearbeitet:
Der Rückgabewert im Fehlerfall ist bei diesem Code undefiniert - es wird das Zurückgegeben, was sich am Ende der Funktion im entsprechenden CPU Register befindet. Wie wäre es mit NAN als Rückgabewert?
 
4 Optionen:

  1. Wie von ph4nt0m vorgeschlagen exceptions verwenden
  2. Wie von Simpson474 vorgeschlagen einen guard value verwenden, der sich eindeutig von einem "normalen" Rückgabewert unterscheidet.
  3. Statt eines doubles ein std:: pair< double, bool > zurückgeben, wobei der Boolean dann true ist, wenn alles stimmt, und false, wenn was nicht paßt.
  4. Per Referenz einen out-Parameter mitgeben, den die Funktion mit einem Error-Status beschreibt.
 
5. Einen bool zurückgeben und das Ergebnis per Referenz-Parameter zurückschreiben.
Hätte den Vorteil, dass man
a) im Fehlerfall nur false returnen muss
b) das ganze im Code meiner Meinung nach recht schön zu benutzen ist.


Edit: Ich schlage jetzt mal besser nicht vor, im Fehlerfall einfach einen Null-Pointer zu dereferenzieren, solange Performance nicht irgendwie extrem wichtig ist. :D
 
Zuletzt bearbeitet:
Einen Booleschen Wert zurückzugeben, wenn die Funktion keine Ja/Nein-Frage beantworten soll, halte ich für eine ziemlich bescheuerte Idee. Nullzeigerdereferenzierung klingt da schon deutlich besser (aber auch nur im Vergleich).
 
Einen Null-Pointer dereferenzieren? Was soll das bringen? So weit mir bekannt, würde das einfach nur undefiniertes Verhalten verursachen.
 
antred schrieb:
Einen Null-Pointer dereferenzieren? Was soll das bringen? So weit mir bekannt, würde das einfach nur undefiniertes Verhalten verursachen.
Nur, wenn das Betriebssystem vergessen hat, einen Page Fault Handler anzulegen. In allen anderen Fällen wird das Programm definiert zwangsbeendet.

€: Äh nein. Ohne Page Fault Handler gibt es natürlich einen definierten Triple Fault mit Reboot des Systems.
 
Vielen Dank schonmal für die Antworten.

-Die Funktion soll auf jeden Fall einen double zurückgeben, das ist in der Aufgabenstellung so vorgegeben.
-NAN ist bei mir irgendwie nicht definiert, obwohl ich <cmath> included hab. Hab aber gelesen, dass NAN!=NAN ist, deswegen könnte ich darauf ja sowieso nicht prüfen.
-Bleibt noch die Möglichkeit mit dem Error-Parameter per Referenz, aber da bräuchte ich ja für jede Funktion (das ist nicht die einzige) eine Variable bzw. ein Array für alle, das erscheint mir unschön

Werde deshalb mal die Variante mit Exceptions ausprobieren ;)
 
asdfman schrieb:
Nur, wenn das Betriebssystem vergessen hat, einen Page Fault Handler anzulegen. In allen anderen Fällen wird das Programm definiert zwangsbeendet.

€: Äh nein. Ohne Page Fault Handler gibt es natürlich einen definierten Triple Fault mit Reboot des Systems.

Auszug aus Kapitel 8.3.2.5 des Working Draft, Standard for Programming
Language C ++
vom November 2014:

[...] [Note: in particular, a null reference cannot exist in a well-defined program, because the only way
to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer,
which causes undefined behavior
. [...]
 
Ok. Wenn wir den Boden der praktischen Realität verlassen wollen, können wir uns auch darauf einigen, dass C++ eine unentscheidbare Grammatik hat und deshalb nicht einmal geparst, geschweige denn kompiliert werden kann.

€: QUALLE!
 
asdfman schrieb:
Ok. Wenn wir den Boden der praktischen Realität verlassen wollen, können wir uns auch darauf einigen, dass C++ eine unentscheidbare Grammatik hat und deshalb nicht einmal geparst, geschweige denn kompiliert werden kann.

€: QUALLE!

Ich verstehe beim besten Willen nicht, was das jetzt mit diesem Thema zu tun hat.
 
antred schrieb:
Ich verstehe beim besten Willen nicht, was das jetzt mit diesem Thema zu tun hat.
Es geht darum, dass im wahren Leben (außerhalb der zitierten Passage aus der Spezifikation) genau das passiert, was ich oben sagte:

Code:
asdf@chelloveck:~/src$ cat nullpo.cpp; g++ -std=c++11 -o nullpo nullpo.cpp; ./nullpo
int main(void) {
        int *p = nullptr;
        *p = 42;
        return 0;
}
Segmentation fault

Der Nullzeiger zeigt auf einen ungültigen Speicherbereich. Die MMU erzeugt einen Page Fault. Das Betriebssystem sendet SIGSEGV. Der Prozess wird zwangsbeendet.
Auf diesen speziellen Fall von "Undefiniertem Verhalten" kann man sich verlassen, außer es handelt sich um einen besonders speziellen Spezialfall, bei dem man dann aber eh weiß, womit man es zu tun hat.

Wenn du dich bei deiner Argumentation auf Umstände berufst, die im Alltag bedeutungslos sind, dann kann ich das auch. Mit meinem Totschlagargument oben.

€: Ratschläge aus der Realität: https://www.owasp.org/index.php/Null_Dereference

They will always result in the crash of the process, unless exception handling (on some platforms) is invoked, and even then, little can be done to salvage the process.[...]
Consequences
Availability: Null-pointer dereferences invariably result in the failure of the process.
 
Zuletzt bearbeitet:
Mit den Exceptions hab ich folgendes Problem: Wenn ich die Funktion ohne try und catch aufrufe und einen Fehler provoziere, bekomme ich einen debug error. Da das eine Klasse ist, soll das aber schon möglich sein.
 
asdfman schrieb:
Es geht darum, dass im wahren Leben (außerhalb der zitierten Passage aus der Spezifikation) genau das passiert, was ich oben sagte:

Code:
asdf@chelloveck:~/src$ cat nullpo.cpp; g++ -std=c++11 -o nullpo nullpo.cpp; ./nullpo
int main(void) {
        int *p = nullptr;
        *p = 42;
        return 0;
}
Segmentation fault

Der Nullzeiger zeigt auf einen ungültigen Speicherbereich. Die MMU erzeugt einen Page Fault. Das Betriebssystem sendet SIGSEGV. Der Prozess wird zwangsbeendet.
Auf diesen speziellen Fall von "Undefiniertem Verhalten" kann man sich verlassen, außer es handelt sich um einen besonders speziellen Spezialfall, bei dem man dann aber eh weiß, womit man es zu tun hat.

Wenn du dich bei deiner Argumentation auf Umstände berufst, die im Alltag bedeutungslos sind, dann kann ich das auch. Mit meinem Totschlagargument oben.

€: Ratschläge aus der Realität: https://www.owasp.org/index.php/Null_Dereference

Das hängt ja wohl mal ganz vom jeweiligen Betriebssystem ab. Es gibt schließlich nicht nur UNIX / Linux und Windows auf der Welt. Soweit es den Standard und portables C++ angeht, ist das Dereferenzieren von Null-Pointer undefiniertes Verhalten, und ich sehe auch nicht, welchen Nutzen das dem OP in seinem Fall überhaupt bringen sollte.
 
@asdfman: Das Problem mit UB ist normalerweise nicht, was die Laufzeitumgebung macht, sondern was der Compiler aufgrund von UB machen darf. Soll heißen, der Compiler darf aus folgendem:

Code:
if (id<0) {
   	double * a = nullptr;
	return *a;
} else {
        return 1.0; 
}
das hier machen:
Code:
   return 1.0 ;

Ist natürlich wieder ein recht hypothetisches Beispiel, aber um es dir UB mal praktisch vor Augen zu führen:
Das hier wird (im release mode) klaglos ausgeführt:

Code:
#include <iostream>

double test(int id) {
	if (id < 0) {
		double * a = nullptr;
		double c = *a;
		return  c;
	}
	else {
		return 1.0;
	}
}
int main() {
	double a = test(-1);
	std::cout << "Hello World" << std::endl;
}

Um einen Fehler darzustellen ist das Dereferenzieren eines Nullpointers also denkbar schlecht geeignet. Und bevor jetzt ein Kommentar wegen Release vs Debug Mode kommt: Ich hoffe du testest deinen Code auch im Release modus.
 
Ok. Das Gespräch fängt an, mich zu interessieren. Würde mich darüber gern mehr unterhalten, ohne den Thread hier entgleisen zu lassen. Könnten wir vielleicht einen Mod dazu bekommen, das Thema in einen neuen Thread auszulagern?

Ad antred:

Das hängt ja wohl mal ganz vom jeweiligen Betriebssystem ab. Es gibt schließlich nicht nur UNIX / Linux und Windows auf der Welt.
Das meinte ich mit Spezialfällen, bei denen der Entwickler weiß, dass es ein Spezialfall ist. Gleiches gilt auch für Systeme ohne MMU undsoweiter. Insofern widersprechen wir uns da nicht.

Soweit es den Standard und portables C++ angeht, ist das Dereferenzieren von Null-Pointer undefiniertes Verhalten
Keinerlei Einspruch von meiner Seite.

und ich sehe auch nicht, welchen Nutzen das dem OP in seinem Fall überhaupt bringen sollte.
Dann spreche ich es für dich mal in Langform aus: Damit wollte ich unterstreichen, was ich von der Idee mit dem Bool halte. Nicht, dass ich es allgemein für eine gute Idee halte.

Ad Miuwa:

Ich halte dein Beispiel für unzutreffend, weil es ein Beispiel für Dead Code Elimination ist. Der Rückgabewert von test() wird verworfen und deshalb kann die Funktion komplett entfernt werden. Das undefinierte Verhalten kommt also niemals zum Tragen.
Wenn ich den Compiler dazu zwinge, die Funktion anzuspringen, kommt wie erwartet:
Code:
asdf@chelloveck:~/src$ cat miuwa.cpp; g++ -std=c++11 -O2 -o miuwa miuwa.cpp; ./miuwa
#include <iostream>

double test(int id) {
        if(id < 0) {
                double *a = nullptr;
                double c = *a;
                return c;
        } else {
                return 1.0;
        }
}

int main() {
        double a = test(-1);
        std::cout << a << std::endl;
}
Segmentation fault
 
Zuletzt bearbeitet:
Wenn die Methode in einer Klasse genutzt wird kann man auch einen Fehlerstatus in der Klasse für die Abfrage einbauen.
Code:
class Vektor
{
     private:
        bool get_error;
     public:
        Vektor();

        double Vektor::get(int index) const;
        inline bool getError() const { return get_error; }
};

Vektor::Vektor()
     : get_error(false)
{
...
}

double Vektor::get(int index) const {

    // Zurücksetzen des Fehlerstatus
    get_error = false;
    // Überprüfung auf existierenden Index
    if ((index < mSize) && (index >= 0)) return mWerte[index]; // Wert zurückgeben

    // Fehlermeldung
    else {
        cerr << "ERROR: index does not exist." << endl;
        // Setzen des Fehlers
        get_error = true;
    }
}

Das wäre eine Möglichkeit. Um die Abfragung nach einem Fehler für den Index außerhalb des Bereichs kommt man nicht drum rum. Eine andere Variante wäre, die übergebene Indexvariable zu "missbrauchen". Was eine eher unschöne Variante ist. Da der Index ja nur positive natürliche Zahlen plus Null verwendet, kann man für den Fehler dem Index eine negative Zahl verpassen. Du definierst ja die Variable index als negative oder positive Ganzzahl. Dafür muss dann eine Referenz verwendet werden.

Code:
double Vektor::get(int &index) const {

    // Überprüfung auf existierenden Index
    if ((index < mSize) && (index >= 0)) return mWerte[index]; // Wert zurückgeben

    // Fehlermeldung
    else {
        cerr << "ERROR: index does not exist." << endl;
        // Setzen des Fehlers
        index = -1;
    }
}

Nach der Rückkehr kann man dann mittels index < 0 auf einen Fehler testen. Aber wir gesagt, sehr unschön und ich würde so etwas auch nicht in einem Programm machen.
 
Wurde hier jetzt ernsthaft 10 Beiträge lang über den Blödsinn mit dem Nullpointer diskutiert? :freak: :D

Microarchitekt schrieb:
Wenn die Methode in einer Klasse genutzt wird kann man auch einen Fehlerstatus in der Klasse für die Abfrage einbauen.
Was einem aber hochkant um die Ohren fliegt, wenn mehrere Threads gleichzeitig auf das Objekt zugreifen.
Selbst wenn einem das egal ist, müsste man get_error noch als mutable deklarieren, damit es in der const-Methode get überhaupt geschrieben werden kann, oder das const rausnehmen, was semantisch aber grob gesagt Schwachsinn ist.

Seppel08 schrieb:
Hab aber gelesen, dass NAN!=NAN ist, deswegen könnte ich darauf ja sowieso nicht prüfen.
Es gäbe da isnan und eine entsprechende Funktion zum Erzeugen von NAN-Werten, also machen könnte man das schon.

asdfman schrieb:
Damit wollte ich unterstreichen, was ich von der Idee mit dem Bool halte.
Ich sage mal so, es hängt natürlich davon ab, was man will. Im konkreten Fall wären Exceptions sicherlich der sinnvollere Weg, weil eine Index-Überschreitung in der Regel nicht gewollt ist und im normalen Betrieb nicht vorkommen sollte, da will ich gar nicht widersprechen.

Vorgeschlagen habe ich es, weil ich das Vorgehen bei ähnlichen Problemen durchaus für sinnvoll halte - wenn ich jetzt irgendwie Daten aus einer Map oder einem Cache oder sowas haben will, wo es in der Regel nicht weiter dramatisch ist, wenn zu dem angefragten Objekt keine Daten existieren, muss ich nicht sofort mit Exceptions um mich werfen, muss aber auch nicht in der Funktion selbst irgendwelche Default-Return-Werte erzeugen, die dann auch noch von dem konkreten Rückgabetypen abhängig wären, was einem wiederum bei Templates Probleme bereiten würde.
 
Zuletzt bearbeitet:
VikingGe schrieb:
Es gäbe da isnan und eine entsprechende Funktion zum Erzeugen von NAN-Werten, also machen könnte man das schon.

Obendrein kann er ja gerade die Tatsache, dass NaN != NaN ist, nutzen um auf NaN zu testen:

Code:
double a = ....

if ( a != a )
{
    // Muss wohl ein NaN sein ...
}
 
Zurück
Oben