Uzasadnienie funkcji biblioteki C nigdy nie ustawia errno na zero

9

Norma C nakazuje, aby żadne funkcje biblioteki standardowej C nie były ustawione errnona zero. Dlaczego to dokładnie jest?

Mogłem zrozumieć, że jest to przydatne do wywoływania kilku funkcji i sprawdzania dopiero errnopo ostatniej - na przykład:

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

Czy nie jest to jednak uważane za „złą praktykę”? Skoro jesteś sprawdzanie tylko errnona samym końcu, trzeba tylko wiedzieć, że jedna z funkcji nie powiedzie, ale nie które Błąd funkcji. Czy po prostu wiedza, że coś zawiodło, wystarcza dla większości praktycznych programów?

Próbowałem szukać różnych dokumentów uzasadnienia C, ale wiele z nich nie zawiera zbyt wielu szczegółów <errno.h>.

Drew McGowen
źródło
1
Myślę, że to „nie płać za to, czego nie chcesz”. Jeśli zależy ci na tym errno, zawsze możesz sam ustawić zero.
Kerrek SB,
2
Należy również pamiętać, że funkcja może errnomieć wartość niezerową, nawet jeśli się powiedzie. (Może wywoływać inne funkcje, które zawodzą, ale nie stanowią awarii funkcji zewnętrznej).
Keith Thompson,

Odpowiedzi:

10

Biblioteka C nie jest ustawiona errnona 0 z powodów historycznych 1 . POSIX nie twierdzi już, że jego biblioteki nie zmienią wartości w przypadku sukcesu, a nowa strona podręcznika systemowego Linuxaerrno.h odzwierciedla to:

Plik <errno.h>nagłówkowy definiuje zmienną całkowitą errnoustawianą przez wywołania systemowe i niektóre funkcje biblioteczne w przypadku błędu wskazującego, co poszło nie tak. Jego wartość jest istotna tylko wtedy, gdy zwracana wartość wywołania wskazuje błąd (tj. -1Z większości wywołań systemowych -1lub NULLz większości funkcji bibliotecznych); funkcja, która udaje się możliwość zmian errno.

Uzasadnienie ANSI C stwierdza, że ​​komitet uznał za bardziej praktyczne przyjęcie i ujednolicenie istniejącej praktyki używania errno.

Maszynę do zgłaszania błędów, skoncentrowaną na ustawieniu, na errnoogół uważa się w najlepszym przypadku z tolerancją. Wymaga to `` patologicznego sprzężenia '' między funkcjami biblioteki i wykorzystuje statyczną komórkę zapisywalną, co zakłóca budowę bibliotek z możliwością udostępniania. Niemniej jednak Komitet wolał ujednolicić istniejącą, choć niewystarczającą maszynę, niż wynaleźć coś bardziej ambitnego.

Prawie zawsze istnieje sposób na sprawdzenie błędu poza sprawdzeniem, czy errnozostał ustawiony. Sprawdzanie, czy errnoustawiony zestaw nie zawsze jest wiarygodne, ponieważ niektóre połączenia wymagają wywołania oddzielnego interfejsu API, aby uzyskać przyczynę błędu. Na przykład ferror()służy do sprawdzania błędu, jeśli otrzymasz krótki wynik z fread()lub fwrite().

Co ciekawe, przykładem użycia strtod()jest jeden z przypadków, w których ustawienie wartości errno0 przed wywołaniem jest wymagane do prawidłowego wykrycia błędu. Wszystkie funkcje strto*()ciąg do liczby mają ten wymóg, ponieważ poprawna wartość zwracana jest zwracana nawet w przypadku wystąpienia błędu.

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? */
    }
}

Powyższy kod opiera się na zachowaniu strtod()zgodnie z dokumentacją w systemie Linux . Norma C stanowi jedynie, że niedomiar nie może zwrócić wartości większej niż najmniejszy dodatni double, a to, czy errnojest ustawiona, ERANGEjest zdefiniowane jako implementacja 2 .

W rzeczywistości istnieje obszerny zapis doradczy certyfikatu, który zaleca zawsze ustawienie errnona 0 przed wywołaniem biblioteki i sprawdzanie jego wartości po wywołaniu wskazującym na awarię . Wynika to z faktu, że niektóre wywołania biblioteki zostaną ustawione, errnonawet jeśli samo wywołanie zakończyło się pomyślnie 3 .

Wartość errno0 podczas uruchamiania programu, ale nigdy nie jest ustawiona na 0 przez żadną funkcję biblioteczną. Wartość errnomoże być ustawiona na niezerową przez wywołanie funkcji biblioteki, niezależnie od tego, czy wystąpił błąd, pod warunkiem, że użycie errnonie jest udokumentowane w opisie funkcji w standardzie C. Program ma sprawdzanie zawartości errnodopiero po zgłoszeniu błędu. Mówiąc dokładniej, errnoma znaczenie tylko wtedy, gdy funkcja biblioteki, która ustawia się errnona błąd, zwróciła kod błędu.


1. Wcześniej twierdziłem, że ma to na celu uniknięcie maskowania błędu z wcześniejszego połączenia. Nie mogę znaleźć żadnych dowodów na poparcie tego roszczenia. Miałem też fałszywy printf()przykład.
2. Dzięki @chux za zwrócenie na to uwagi. Odniesieniem jest C.11 § 7.22.1.3 ¶10.
3. Wskazane przez @KeithThompson w komentarzu.

jxh
źródło
Drobny problem: wydaje się, że niedopełnienie może skutkować wynikiem innym niż 0: „funkcje zwracają wartość, której wielkość nie jest większa niż najmniejsza znormalizowana liczba dodatnia w typie zwracanym” C11 7.22.1.3.10.
chux - Przywróć Monikę
@chux: Dzięki. Dokonam edycji. Ponieważ ustawienie errnosię ERANGEjest wdrożenie zdefiniowane w przypadku niedopełnienia, w rzeczywistości nie ma przenośny sposób wykryć niedomiar. Mój kod był zgodny z tym, co znalazłem na stronie podręcznika systemu Linux w moim systemie.
jxh
Twoje komentarze na temat strto*funkcji są dokładnie powodem, dla którego spytałem, czy mój przykład byłby uważany za złą praktykę, ale jest to jedyny sposób, w którym errnonieustawienie wartości zero byłoby przydatne, a nawet zastosowanie.
@DrewMcGowen: Wydaje mi się, że jedyną rzeczą, jaką możesz wziąć, jest to, że errnomożna ustawić nawet w przypadku sukcesu, więc samo użycie tego, że zostało ustawione, nie jest tak naprawdę dobrym wskaźnikiem, że wystąpił błąd. Musisz sprawdzić wyniki poszczególnych połączeń, aby wiedzieć, czy w ogóle wystąpił błąd.
jxh
1

Możesz naprawdę sprawdzić błędy obu wywołań funkcji, jeśli naprawdę Ci na tym zależy.

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

Ponieważ nigdy nie wiemy, jak wywołała się funkcja mach w procesie, w jaki sposób lib może ustawić errnos dla każdego wywołania funkcji, prawda?


źródło
1

Arbitralna zmiana błędu no jest analogiczna do „łapania i połykania” wyjątku. Zanim zdarzały się wyjątki, które rozprzestrzeniałyby się przez różne warstwy programu i ostatecznie osiągnęły punkt, w którym osoba dzwoniąca albo złapie i zareaguje na wyjątek w jakiś sposób, albo po prostu przejdzie za niego, były błędy. Nie zmienianie errno, chyba że w jakiś sposób obsługujesz, zastępujesz, traktujesz jako nieistotny błąd, jest niezbędny do tego paradygmatu propagacji obsługi błędów pierwszej generacji.

Andyz Smith
źródło