Обоснование того, что функции библиотеки C никогда не устанавливают errno на ноль

9

Стандарт C требует, чтобы никакие функции стандартной библиотеки C не устанавливались errnoв ноль. Почему именно это?

Я мог бы понять, что это полезно для вызова нескольких функций и проверки только errnoпосле последней, например:

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

Однако не считается ли это "плохой практикой"? Так как вы только проверка errnoв самом конце, вы знаете только , что одна из функций действительно терпят неудачу, но не которые функционируют не удалось. Просто знание того, что что- то не получилось достаточно хорошо для большинства практических программ?

Я пытался найти различные документы C Rationale, но у многих из них нет подробностей <errno.h>.

Дрю Макгоуэн
источник
1
Я думаю, это "не плати за то, что ты не хочешь". Если вам небезразлично errno, вы всегда можете установить его на ноль самостоятельно.
Kerrek SB
2
Что еще нужно знать, это то, что функция может установить errnoненулевое значение, даже если это удастся. (Это может вызвать другую функцию, которая не работает, но это не является ошибкой во внешней функции.)
Кит Томпсон

Ответы:

10

Библиотека C не устанавливается errnoв 0 по историческим причинам 1 . POSIX больше не утверждает, что его библиотеки не изменят значение в случае успеха, и новая справочная страница Linux дляerrno.h отражает это:

<errno.h>Файл заголовка определяет целочисленную переменную errno, которая устанавливается с помощью системных вызовов и библиотечные функции в случае ошибки , чтобы указать , что пошло не так. Его значение имеет значение только тогда, когда возвращаемое значение вызова указывает на ошибку (т. Е. -1От большинства системных вызовов -1или NULLот большинства функций библиотеки); Успешная функция может измениться errno.

ANSI C Обоснование утверждает , что Комитет счел более целесообразным принять и стандартизировать существующую практику использования errno.

Механизм сообщения об ошибках, сконцентрированный на настройке, errnoобычно рассматривается в лучшем случае с допуском. Это требует «патологической связи» между библиотечными функциями и использует статическую ячейку памяти с возможностью записи, что мешает созданию разделяемых библиотек. Тем не менее Комитет предпочел стандартизировать этот существующий, однако несовершенный механизм, а не изобретать что-то более амбициозное.

Почти всегда есть способ проверить на наличие ошибок, кроме проверки, если errnoона установлена. Проверка, errnoполучен ли набор, не всегда надежен, поскольку некоторые вызовы требуют вызова отдельного API, чтобы получить причину ошибки. Например, ferror()используется для проверки ошибки, если вы получаете короткий результат от fread()или fwrite().

Интересно, что ваш пример использования strtod()- это один из случаев, когда для правильного обнаружения возникновения ошибки требуется установить errnoзначение 0 перед вызовом . Все функции со строкой в ​​число имеют это требование, потому что верное возвращаемое значение возвращается даже в случае ошибки.strto*()

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

Приведенный выше код основан на поведении, описанном strtod()в Linux . Стандарт C предусматривает только то, что underflow не может возвращать значение, превышающее наименьшее положительное значение double, и errnoзадано или нет значение, ERANGEопределенное реализацией 2 .

На самом деле существует обширная консультативная запись сертификата, которая рекомендует всегда устанавливать errnoзначение 0 перед вызовом библиотеки и проверять его значение после вызова, чтобы указать, что произошла ошибка . Это связано с тем, что некоторые вызовы библиотеки будут установлены, errnoдаже если сам вызов был успешным 3 .

При errnoзапуске программы значение равно 0, но оно никогда не устанавливается равным 0 какой-либо библиотечной функцией. Значение errnoможет быть установлено в ненулевое значение при вызове библиотечной функции независимо от того, есть ли ошибка, при условии, что ее использование errnoне описано в описании функции в Стандарте C. Для программы имеет смысл проверять содержимое errnoтолько после сообщения об ошибке. Точнее, errnoимеет смысл только после того, как библиотечная функция, которая устанавливает errnoошибку, вернула код ошибки.


1. Ранее я утверждал, что это было во избежание маскировки ошибки от более раннего вызова. Я не могу найти никаких доказательств в поддержку этого утверждения. У меня также был фиктивный printf()пример.
2. Спасибо @chux за указание на это. Ссылка C.11 §7.22.1.3 ¶10.
3. Указано @KeithThompson в комментарии.

jxh
источник
Незначительная проблема: кажется, что недостаточное значение может привести к результату, отличному от 0: «функции возвращают значение, величина которого не превышает наименьшего нормализованного положительного числа в типе возврата» C11 7.22.1.3.10.
chux - Восстановить Монику
@chux: Спасибо. Я сделаю правку. Так как установка errnoв ERANGE- это реализация, определенная в случае недостаточного значения, на самом деле нет никакого портативного способа обнаружения недостаточного значения. Мой код соответствовал тому, что я нашел на странице руководства Linux в моей системе.
JXH
Ваши комментарии о strto*функциях - именно поэтому я и спросил, будет ли мой пример считаться плохой практикой, но это единственный способ, при котором errnoотсутствие нуля было бы полезно или даже применимо.
@DrewMcGowen: Полагаю, единственное, что вы можете сделать, это то, что errnoможет быть установлено даже в случае успеха, поэтому просто использование того факта, что оно установлено, не является достаточно хорошим индикатором того, что произошла ошибка. Вы должны сами проверить результаты отдельных звонков, чтобы узнать, произошла ли вообще ошибка.
JXH
1

Вы можете сделать проверку ошибок для обоих вызовов функций, если вы действительно заботитесь об этом.

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

Поскольку мы никогда не знаем, как функция mach вызывается в процессе, как библиотека может устанавливать errnos для каждого вызова функции, верно?


источник
1

Произвольное изменение errorno аналогично «отлову и проглатыванию» исключения. До того, как были исключения, которые распространялись через разные уровни программы и, наконец, достигали точки, когда вызывающая сторона либо ловила и реагировала на исключение тем или иным способом, либо просто передавала деньги, были ошибки. Не изменяйте ошибку, если только вы не обрабатываете, не переопределяете, не рассматриваете ошибку как несущественную, что существенно для этой парадигмы распространения ошибок первого поколения.

Энди Смит
источник