Begründung für C-Bibliotheksfunktionen, die errno niemals auf Null setzen

9

Der C-Standard schreibt vor, dass keine C-Standardbibliotheksfunktionen errnoauf Null gesetzt werden dürfen. Warum genau ist das so?

Ich könnte verstehen, dass es nützlich ist, mehrere Funktionen aufzurufen und erst errnonach der letzten zu überprüfen - zum Beispiel:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

Wird dies jedoch nicht als "schlechte Praxis" angesehen? Da Sie nur sind die Überprüfung errnoam Ende, Sie wissen nur , dass eine der Funktionen haben scheitern, aber nicht die gescheiterten funktionieren. Ist es einfach gut genug zu wissen, dass etwas für die meisten praktischen Programme fehlgeschlagen ist?

Ich habe versucht, nach verschiedenen C Rationale-Dokumenten zu suchen, aber viele von ihnen haben nicht viele Details <errno.h>.

Drew McGowen
quelle
1
Ich denke, es ist "nicht bezahlen für das, was du nicht willst". Wenn Sie sich interessieren errno, können Sie es jederzeit selbst auf Null setzen.
Kerrek SB
2
Beachten Sie außerdem, dass eine Funktion auch dann errnoauf einen Wert ungleich Null gesetzt werden kann, wenn dies erfolgreich ist. (Es könnte eine andere Funktion aufrufen, die fehlschlägt, aber dies stellt keinen Fehler in der äußeren Funktion dar.)
Keith Thompson

Antworten:

10

Die C-Bibliothek wird errnoaus historischen Gründen nicht auf 0 gesetzt 1 . POSIX behauptet nicht mehr, dass seine Bibliotheken den Wert im Erfolgsfall nicht ändern werden, und die neue Linux-Manpage fürerrno.h spiegelt dies wider:

Die <errno.h>Header-Datei definiert die Ganzzahlvariable errno, die durch Systemaufrufe und einige Bibliotheksfunktionen im Fehlerfall festgelegt wird, um anzuzeigen, was schief gelaufen ist. Sein Wert ist nur dann von Bedeutung, wenn der Rückgabewert des Aufrufs einen Fehler anzeigt (dh -1von den meisten Systemaufrufen -1oder NULLvon den meisten Bibliotheksfunktionen). eine Funktion , die erfolgreich ist , zu ändern erlaubt errno.

Die ANSI C-Begründung besagt, dass der Ausschuss es für praktischer hielt, die bestehende Verwendungspraxis zu übernehmen und zu standardisieren errno.

Die Fehlermeldemaschinerie, die sich auf die Einstellung von konzentriert, errnowird im Allgemeinen bestenfalls mit Toleranz betrachtet. Es erfordert eine "pathologische Kopplung" zwischen Bibliotheksfunktionen und verwendet eine statische beschreibbare Speicherzelle, die den Aufbau gemeinsam nutzbarer Bibliotheken stört. Der Ausschuss zog es jedoch vor, diese vorhandenen, jedoch mangelhaften Maschinen zu standardisieren, anstatt etwas Ehrgeizigeres zu erfinden.

Es gibt fast immer eine Möglichkeit, außerhalb der Überprüfung auf Fehler auf Fehler zu prüfen errno. Das Überprüfen, ob errnogesetzt wurde, ist nicht immer zuverlässig, da für einige Aufrufe eine separate API aufgerufen werden muss, um den Fehlergrund zu ermitteln. ferror()Wird beispielsweise verwendet, um nach einem Fehler zu suchen, wenn Sie ein kurzes Ergebnis von fread()oder erhalten fwrite().

Interessanterweise ist Ihr Beispiel für die Verwendung strtod()einer der Fälle, in denen die Einstellung errnoauf 0 vor dem Aufruf erforderlich ist , um korrekt zu erkennen, ob ein Fehler aufgetreten ist. Für alle strto*()String-to-Number-Funktionen gilt diese Anforderung, da auch bei einem Fehler ein gültiger Rückgabewert zurückgegeben wird.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

Der obige Code basiert auf dem Verhalten von strtod()Linux . Der C-Standard schreibt nur vor, dass der Unterlauf keinen Wert zurückgeben kann, der größer als das kleinste Positiv ist double, und ob die errnoEinstellung ERANGEfestgelegt ist oder nicht, ist in der Implementierung definiert 2 .

Tatsächlich gibt es eine umfangreiche Beschreibung der Zertifizierungsempfehlungen, in der empfohlen wird errno, vor einem Bibliotheksaufruf immer den Wert 0 festzulegen und den Wert nach dem Aufruf zu überprüfen, um anzuzeigen, dass ein Fehler aufgetreten ist . Dies liegt daran, dass einige Bibliotheksaufrufe auch dann gesetzt werden errno, wenn der Aufruf selbst erfolgreich war 3 .

Der Wert von errnoist beim Programmstart 0, wird jedoch von keiner Bibliotheksfunktion auf 0 gesetzt. Der Wert von errnokann durch einen Bibliotheksfunktionsaufruf auf ungleich Null gesetzt werden, unabhängig davon, ob ein Fehler vorliegt oder nicht, vorausgesetzt, die Verwendung von errnoist in der Beschreibung der Funktion im C-Standard nicht dokumentiert. Für ein Programm ist es sinnvoll, den Inhalt von errnoerst zu überprüfen, nachdem ein Fehler gemeldet wurde. Genauer gesagt errnoist dies nur dann sinnvoll, wenn eine Bibliotheksfunktion, die errnoeinen Fehler festlegt, einen Fehlercode zurückgegeben hat.


1. Früher habe ich behauptet, es sollte vermieden werden, einen Fehler aus einem früheren Anruf zu maskieren. Ich kann keine Beweise finden, die diese Behauptung stützen. Ich hatte auch ein falsches printf()Beispiel.
2. Vielen Dank an @chux für diesen Hinweis. Referenz ist C.11 §7.22.1.3 ¶10.
3. In einem Kommentar von @KeithThompson darauf hingewiesen.

jxh
quelle
Kleines Problem: Es scheint, dass ein Unterlauf zu einem Ergebnis ungleich 0 führen kann: "Die Funktionen geben einen Wert zurück, dessen Größe nicht größer als die kleinste normalisierte positive Zahl im Rückgabetyp ist." C11 7.22.1.3.10.
chux
@chux: Danke. Ich werde eine Bearbeitung vornehmen. Da die Einstellung errnoauf ERANGEdie Implementierung im Falle eines Unterlaufs definiert ist, gibt es tatsächlich keine tragbare Möglichkeit, einen Unterlauf zu erkennen. Mein Code folgte dem, was ich in der Linux-Manpage auf meinem System gefunden habe.
Jxh
Ihre Kommentare zu den strto*Funktionen sind genau der Grund, warum ich gefragt habe, ob mein Beispiel als schlechte Praxis angesehen wird, aber es ist die einzige Möglichkeit, errnonicht auf Null gesetzt zu werden oder sogar anzuwenden.
@DrewMcGowen: Ich denke, der einzige Punkt, den Sie ansprechen können, ist, dass er errnoselbst im Erfolgsfall gesetzt werden kann. Die Tatsache, dass er gesetzt wurde, ist also nicht gut genug, um anzuzeigen, dass ein Fehler aufgetreten ist. Sie müssen die Ergebnisse der einzelnen Anrufe selbst überprüfen, um festzustellen, ob überhaupt ein Fehler aufgetreten ist.
Jxh
1

Sie können für beide Funktionsaufrufe eine Fehlerprüfung durchführen, wenn Sie sich wirklich darum kümmern.

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

Da wir nie wissen, wie die mach-Funktion in einem Prozess aufgerufen wird, wie kann die lib Fehler für jeden Funktionsaufruf setzen, richtig?


quelle
1

Das willkürliche Ändern des Errornos ist analog zum "Fangen und Schlucken" einer Ausnahme. Bevor es Ausnahmen gab, die sich über verschiedene Ebenen eines Programms ausbreiteten und schließlich einen Punkt erreichten, an dem der Aufrufer die Ausnahme entweder abfängt und auf irgendeine Weise reagiert oder einfach das Geld übergibt, gab es Fehler. Es ist für dieses Paradigma der Fehlerbehandlung der ersten Generation von wesentlicher Bedeutung, den Fehler nicht zu ändern, es sei denn, Sie behandeln den Fehler irgendwie, überschreiben ihn und behandeln ihn als irrelevant.

Andyz Smith
quelle