Лучше ли реализовать strtol() без errno?

Традиционный strtol() обычно используется так:

int main()
{
    errno = 0;
    char *s = "12345678912345678900";
    char *endptr;
    long i = strtol(s,  &endptr, 10);
    if(i == LONG_MAX && errno == ERANGE) 
        printf("overflow");
}

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

Итак, не лучше ли реализовать strtol без errno, но используя какие-то другие способы индикации переполнения?

как:

long strtol(const char *nptr, char **endptr, int base, bool *is_overflow);

вместо

long strtol(const char *nptr, char **endptr, int base);

person weiweishuo    schedule 09.12.2018    source источник
comment
Я не уверен, в чем вопрос. Вы не реализуете strtol, вы его используете. Вы спрашиваете, следует ли вам переопределить strtol, чтобы он не использовал errno?   -  person user202729    schedule 09.12.2018
comment
Я считаю, что Posix требует errno, поэтому это невозможно сделать. Кроме того, вы потратите большую часть своего времени на алгоритм конвертации. Преобразование, вероятно, выполняется в O(n). Параметр errno выполняется в O(1). Параметр errno в основном не имеет значения.   -  person jww    schedule 09.12.2018
comment
Если нет errno, как узнать, не было ли переполнения?   -  person DYZ    schedule 09.12.2018
comment
Поправьте меня, если я ошибаюсь, но вам не нужно устанавливать errno равным нулю; -поскольку это rvalue, это было бы неправильно-.   -  person Shmuel H.    schedule 09.12.2018
comment
@ШмуэльХ. Это lvalue (функция, возвращающая указатель, замаскированный под переменную).   -  person DYZ    schedule 09.12.2018
comment
@user202729 user202729 спасибо за напоминание, я действительно хочу реализовать свой strol, поэтому я задал этот вопрос. @DYZ спасибо, я отредактировал последнюю строку.   -  person weiweishuo    schedule 09.12.2018
comment
enptr должно быть endptr.   -  person user202729    schedule 09.12.2018
comment
Связано, вот strtol.c реализация от libc.   -  person jww    schedule 09.12.2018
comment
Э-э, реализация по умолчанию не так эффективна. Вы смотрели на разборку? __errno_location вызывается только один раз.   -  person user202729    schedule 09.12.2018
comment
@user202729 user202729 С параметром -O2 местоположение errno вычислялось и кэшировалось. С -O0 дважды вызывается __errno_location.   -  person DYZ    schedule 09.12.2018
comment
Нет причин инициализировать endptr. При использовании strtol у вас есть 2 проверки (1) if (s == endptr && i = 0) { /* error no digits converted */ }, а затем (2) else if (errno) { /* over/underflow occurred */ }. Итак, нет, не лучше избегать использования errno.   -  person David C. Rankin    schedule 09.12.2018
comment
@weiweishuo Если вам важна производительность, вы не будете компилировать с -O0. Очевидно.   -  person user202729    schedule 09.12.2018
comment
@DavidC.Rankin Выглядит подходящим для публикации в ответе;; хотя OP обеспокоен тем, что вызов функции __errno_location (или эквивалентный) может быть медленным.   -  person user202729    schedule 09.12.2018
comment
Проблема с производительностью вызывает странную озабоченность, поскольку в лучшем случае она будет реализована как макрос, устанавливающий глобальную переменную на основе простого поиска. Я бы не ожидал, что это будет иметь какие-либо проблемы с производительностью.   -  person David C. Rankin    schedule 09.12.2018
comment
@DavidC.Rankin отредактировал код, спасибо.   -  person weiweishuo    schedule 09.12.2018
comment
@DavidC.Rankin Зачем предлагать if (s == endptr && i = 0), а не просто if (s == endptr)?   -  person chux - Reinstate Monica    schedule 09.12.2018
comment
Просто потому, что так говорит справочная страница? Я всегда думал, что if (s == endptr) это нормально (и я уверен, что это так), но на странице руководства написано if (s == endptr && i = 0), поэтому для полноты я включаю его. (например, "stores the original value of nptr in *endptr (and returns 0)")   -  person David C. Rankin    schedule 09.12.2018
comment
errno не является хорошим методом сообщения об ошибках. Если вы не привязаны к существующему интерфейсу, лучше использовать другой метод. Не вызывайте свою функцию strtol и используйте любой метод, который вы хотите. Но strtol высечен в камне.   -  person n. 1.8e9-where's-my-share m.    schedule 09.12.2018
comment
@weiweishuo - если вас не интересует, является ли возврат LONG_MIN или LONG_MAX, второе сравнение с участием errno не требуется. В любом случае установлено errno и достаточно проверить, что errno не равно нулю. Кроме того, после установки errno вы не подвергаетесь дополнительным штрафам за доступ к его значению (не больше, чем к доступу к значению любой переменной).   -  person David C. Rankin    schedule 09.12.2018
comment
Реализовать свой собственный вариант strtol() в коде C для вас нечестная гонка. Вы сталкиваетесь с strtol(), который реализован на C, ассемблере или чем-то еще и создан для быстрой работы на этой платформе.   -  person chux - Reinstate Monica    schedule 09.12.2018
comment
errno гарантируется lvalue, фактическая реализация совершенно не имеет значения. Если это макрос, вызывающий функцию и разыменовывающий возвращаемый int *, компилятор может оптимизировать несколько вызовов. Оставьте такие оптимизации инструментальной цепочке, если у вас нет причин поступать иначе. Однако это едва ли когда-либо является узким местом. Не делайте преждевременных оптимизаций. Если у вас возникли проблемы с производительностью, профилируйте и оптимизируйте выявленные точки доступа.   -  person too honest for this site    schedule 09.12.2018
comment
@DavidC.Rankin: errno находится в TLS, поэтому обычно в таблице символов выполняется поиск, если потоки поддерживаются. Однако обычно это делается только один раз в функции (или даже в большем масштабе) и вряд ли является проблемой в реальном коде, особенно в контексте синтаксического анализа строк.   -  person too honest for this site    schedule 09.12.2018


Ответы (2)


лучше ли реализовать strtol без errno...

No.

... но использовать другие способы индикации переполнения?

No.

long int strtol(const char * restrict nptr, char ** restrict endptr, int base);

strtol() — это стандартная библиотечная функция C, и любая реализация должна придерживаться правильного использования 3 входов и errno, чтобы соответствовать требованиям.


Конечно, OP может реализовать некоторые другие my_strtol() по желанию.

Любые проблемы с производительностью, связанные с тем, чтобы избежать errno, являются микрооптимизацией, но разумной целью дизайна.

На самом деле все сводится к тому, как передать проблемы строки в long.

  • Переполнение "12345678912345678901234567890"

  • Нет конверсий "abc"

  • Лишний хлам "123 abc"

  • Допускается начальный пробел, разрешается конечный пробел?

  • Разрешить различные базы?

После определения функциональности для всех исключительных случаев, а не только для переполнения, вопросы кодирования errno полезны, даже если маловероятно, что они приведут к каким-либо значимым улучшениям производительности.

ИМО, кодирование только для одной базы, вероятно, является более продуктивным путем к повышению скорости, чем errno.


Код OP не является надежным strtol() использованием. Предложить:

char *s = "12345678912345678900";

char *endptr;
errno = 0;
long i = strtol(s,  &endptr, 10);
if (errno == ERANGE) printf("Overflow %ld\n", i);
else if (s == endptr) printf("No conversion %ld\n", i);
else if (*endptr) printf("Extra Junk %ld\n", i);
else printf("Success %ld\n", i);
person chux - Reinstate Monica    schedule 09.12.2018

На самом деле помимо errno в strtol() есть некоторые накладные расходы, такие как пропуск пробелов, забота о базе (10 или шестнадцатеричный), контрольные символы...

В конкретной среде, где скорость имеет решающее значение и вы знаете, что предоставленная строка представляет собой число с основанием 10, которое соответствует long, вы можете создать свою собственную быструю функцию, например

#include <ctype.h>

long mystrtol(char *s) {
   long res = 0, minus = *s == '-';
   if (minus || *s == '+') s++;

   while (isdigit(*s)) {
      res = res*10 + (*s++ - '0');
   }

   return minus ? -res : res;
}

и выберите встроить его.

person Breaking not so bad    schedule 09.12.2018
comment
Нит, но +10 также является допустимым числовым представлением, которое обрабатывается. Крайние случаи — это то, что делает использование стандартных библиотечных функций предпочтительнее, чем использование ваших собственных. - person David C. Rankin; 09.12.2018
comment
Верно ; Я думаю, если скорость настолько критична, и они знают, что перед строкой не стоит бесполезный +... они также могут заменить isdigit(*s) на *s... - person Breaking not so bad; 09.12.2018
comment
Да, это не должно было отвлекать вас от вашего алгоритма, все в порядке, я думаю, что главное для ОП заключается в том, что если вы не можете исключить все крайние случаи и свернуть свои собственные, чтобы соответствовать одному конкретному преобразованию, тогда просто трудно победить годы разработки и оптимизации уже встроенных в библиотеку функций. Даже тогда, спустя годы, кто-то запускает код против обычного варианта, который не охватывается, и начинается ад. Заботиться о микрооптимизации — это в лучшем случае русская рулетка. - person David C. Rankin; 09.12.2018
comment
Угловые проблемы: isdigit(*s) является техническим UB, когда *s < 0 (приведено к unsigned char) . Далее res = res*10 + (*s++ - '0'); является UB (целочисленное переполнение со знаком), когда целевой результат находится в диапазоне LONG_MIN. Обычный UB в этих случаях подходит, но все же UB. - person chux - Reinstate Monica; 09.12.2018
comment
В дополнение к уже перечисленным проблемам, функция должна быть const-корректной, т.е. использовать const. Многие разумные стандарты кодирования требуют указателей на строковые литералы, объявленные таким образом (и это хорошая практика на большинстве платформ). Кроме того, предпосылка в лучшем случае сомнительна. Стандартные библиотечные функции часто реализуются высокоэффективно, вплоть до оптимизированного кода на ассемблере. Также гибкость оригинала может быть приятным дополнением для пользователя (например, возможность использовать другие базы). - person too honest for this site; 09.12.2018
comment
@chux не технический UB, а серьезный UB совершенно непредсказуемого поведения на любых платформах, использующих подписанные символы, если вводится отрицательный байт... - person Antti Haapala; 09.12.2018
comment
Правильным использованием isdigit является только следующее: isdigit((unsigned char)*s). - person Antti Haapala; 09.12.2018
comment
Отредактировано, чтобы подчеркнуть (выделено жирным шрифтом), в каких условиях должна использоваться функция. - person Breaking not so bad; 09.12.2018