Обґрунтування функцій бібліотеки C ніколи не встановлює errno до нуля


9

Стандарт 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в самому кінці, ви знаєте лише, що одна з функцій вийшла з ладу, але не яка функція вийшла з ладу. Невже просто знати, що щось не вдалося досить добре для більшості практичних програм?

Я намагався шукати різні документи з мотивів С, але багато з них не мають багато деталей <errno.h>.


1
Я думаю, це "не платити за те, чого ти не хочеш". Якщо вам все одно errno, ви завжди можете встановити його на нуль.
Керрек СБ

2
Ще щось, про що слід пам’ятати, - це те, що функція може встановлювати errnoненульове значення, навіть якщо це успішно. (Це може викликати якусь іншу функцію, яка виходить з ладу, але це не означає збій у зовнішній функції.)
Кіт Томпсон,

Відповіді:


10

Бібліотека С не встановлена 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 лише передбачає, що під потоком не може повернутись значення, що перевищує найменший позитивний показник double, і встановлено, чи не errnoвизначено ERANGEреалізацією 2 .

Насправді існує обширний довідковий запис cert, який рекомендує завжди встановлювати 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 у коментарі.


Незначна проблема: виявляється, що під переповненням може призвести до не-0 результату: "функції повертають значення, величина якого не перевищує найменшого нормованого додатного числа у типі повернення" C11 7.22.1.3.10.
chux

@chux: Дякую Я зроблю редагування. Оскільки налаштування errnoto ERANGE- це реалізація, визначена у випадку підтоплення, фактично немає портативного способу виявлення підливу. Мій код слідував за тим, що я знайшов на сторінці man в моїй системі 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 у процесі, як lib може встановити errnos для кожної функції, викликати, правда?


1

Довільна зміна помилки є аналогом виключення "ловити та ковтати". До того, як були винятки, які поширюватимуться через різні шари програми і, нарешті, досягають точки, коли абонент як-небудь піймає та відповість на виняток якимось чином, чи просто передасть долар, були помилки. Не змінюючи errno, якщо ви якимось чином не обробляєте, переосмислюєте, трактуєте як неактуальну помилку, є важливим для цієї парадигми поширення помилок першого покоління.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.