Различие между scanf () и стертолом () / strtod () в парсинге чисел

Это - главным образом персональная проблема стиля. Лично, пока where пункт соответствует на одной строке, я группирую пункты.

Используя приблизительно where с будет иметь тенденцию быть менее производительной, потому что она требует дополнительного вызова делегата для каждого элемента, который делает ее настолько далеко. Однако это, вероятно, будет незначительной проблемой и должно только быть рассмотрено, если профилировщик покажет его, чтобы быть проблемой.

12
задан DevSolar 17 September 2009 в 09:31
поделиться

8 ответов

Общение с Фредом Дж. Тайдманом, заместителем председателя PL22.11 (ANSI "C"), на comp.std.c пролило некоторый свет на это:

fscanf

Элемент ввода определяется как самая длинная последовательность вводимых символов [...] который является или является префиксом соответствующая входная последовательность. (7.19.6.2 P9)

Это делает "0x" самой длинной последовательностью, которая является префиксом соответствующей входной последовательности. (Даже с преобразованием % i , поскольку шестнадцатеричный «0x» является более длинной последовательностью, чем десятичный «0».)

Первый символ, если есть, после элемент ввода остается непрочитанным. (7.19.6.2 P9)

Это заставляет fscanf прочитать букву «z» и вернуть ее как несоответствующую (соблюдая ограничение в один символ в сноске 251)).

Если элемент ввода не соответствует последовательность, выполнение директива не выполняется: это условие сбой сопоставления. (7.19.6.2 P10)

Это приводит к тому, что "0x" не может соответствовать, т.е. fscanf не должен назначать никакого значения, возвращать ноль (если % x или % i был первым спецификатором конверсии) и оставил «z» в качестве первого непрочитанного символа во входном потоке.

strtol

Определение strtol strtoul ) отличается в одном решающем моменте:

Субъектная последовательность определяется как самая длинная начальная подпоследовательность строка ввода, начиная с первой символ без пробела, , имеющий ожидаемая форма . (7.20.1.4 P4, выделено мной)

Это означает, что strtol должен искать самую длинную действительную последовательность, в данном случае «0». Он должен указывать endptr на «x» и возвращать ноль в качестве результата.

6
ответ дан 2 December 2019 в 21:23
поделиться

I don't believe the parsing is allowed to produce different results. The Plaugher reference is just pointing out that the strtol() implementation might be a different, more efficient version as it has complete access to the entire string.

3
ответ дан 2 December 2019 в 21:23
поделиться

Я не уверен, что понимаю вопрос, но, во-первых, scanf () должен обрабатывать EOF. scanf () и strtol () - разные чудовища. Может, стоит вместо этого сравнить strtol () и sscanf ()?

0
ответ дан 2 December 2019 в 21:23
поделиться

Согласно спецификации C99, семейство функций scanf () анализирует целые числа так же, как семейство функций strto * () . Например, для спецификатора преобразования x это читается так:

Соответствует необязательно подписанному шестнадцатеричное целое число в формате то же, что и ожидалось для предмета последовательность функции strtoul с значение 16 для аргумента base .

Итак, если sscanf () и strtoul () дают разные результаты, реализация libc не соответствует.

Какими должны быть ожидаемые результаты вашего примера кода , немного неясно:

strtoul () принимает необязательный префикс 0x или ] 0X , если база равна 16 , а в спецификации указано

Подлежащая последовательность определяется как самая длинная начальная подпоследовательность строка ввода, начиная с первой не-пробельный символ, то есть ожидаемая форма.

Для строки «0xz» , на мой взгляд, самая длинная начальная подпоследовательность ожидаемой формы - «0» , поэтому значение должно быть 0 и аргумент endptr должен иметь значение x .

mingw-gcc 4.4.0 не соглашается и не может проанализировать строку с помощью обоих strtoul () и sscanf () . Причина может заключаться в том, что самая длинная начальная подпоследовательность ожидаемой формы - это «0x» - это недопустимый целочисленный литерал, поэтому синтаксический анализ не производится.

Я думаю, что такая интерпретация стандарта неверна: Подпоследовательность ожидаемой формы всегда должна давать допустимое целочисленное значение (если оно выходит за пределы допустимого диапазона, возвращаются значения MIN / MAX , а для errno установлено значение ERANGE ).

cygwin-gcc 3.4.4 (который использует newlib, насколько мне известно) также не будет анализировать литерал, если используется strtoul () , но анализирует строку в соответствии с моей интерпретацией стандарта с помощью sscanf () .

Помните, что моя интерпретация стандарта чревата вашей начальной проблемой, то есть что стандарт гарантирует только возможность ungetc () один раз. Чтобы решить, является ли 0x частью литерала, вы должны прочитать вперед два символа: x и следующий за ним символ. Если это не шестнадцатеричный символ, их нужно вернуть. Если есть еще токены для анализа, вы можете буферизовать их и обойти эту проблему, но если это ' s последний токен, вы должны ungetc () оба символа.

Я не совсем уверен, что fscanf () должен делать, если ungetc () терпит неудачу. Может просто установить индикатор ошибки потока?

3
ответ дан 2 December 2019 в 21:23
поделиться

Я не уверен, как реализация scanf () может быть связана с ungetc (). scanf () может использовать все байты в буфере потока. ungetc () просто помещает байт в конец буфера, и смещение также изменяется.

scanf("%d", &x);
ungetc('9', stdin);
scanf("%d", &y);
printf("%d, %d\n", x, y);

Если на входе «100», на выходе будет «100, 9». Я не понимаю, как scanf () и ungetc () могут мешать друг другу. Извините, если я добавил наивный комментарий.

0
ответ дан 2 December 2019 в 21:23
поделиться

Ответ устарело после переписывания вопроса. Хотя есть несколько интересных ссылок в комментариях.


Если сомневаетесь, напишите тест. - пословица

После тестирования всех комбинаций спецификаторов преобразования и вариантов ввода, которые я мог придумать, Я могу сказать, что это правильно, что два семейства функций не дают идентичных результатов . (По крайней мере, в glibc, которая у меня есть для тестирования.)

Разница появляется, когда встречаются три обстоятельства:

  1. Вы используете «% i» или «% x» (разрешает ввод в шестнадцатеричном формате).
  2. Входные данные содержат (необязательно) шестнадцатеричный префикс «0x» .
  3. После шестнадцатеричного префикса нет действительной шестнадцатеричной цифры.

Пример кода:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    char * string = "0xz";
    unsigned u;
    int count;
    char c;
    char * endptr;

    sscanf( string, "%x%n%c", &i, &count, &c );
    printf( "Value: %d - Consumed: %d - Next char: %c - (sscanf())\n", u, count, c );
    i = strtoul( string, &endptr, 16 );
    printf( "Value: %d - Consumed: %td - Next char: %c - (strtoul())\n", u, ( endptr - string ), *endptr );
    return 0;
}

Вывод:

Value: 0 - Consumed: 1 - Next char: x - (sscanf())
Value: 0 - Consumed: 0 - Next char: 0 - (strtoul())

Это меня смущает. Очевидно, что sscanf () не срабатывает при 'x' , иначе он не сможет проанализировать любые "0x" шестнадцатеричные числа с префиксом . Итак, он прочитал 'z' и обнаружил, что он не соответствует. Но он решает использовать только ведущий «0» в качестве значения. Это означало бы отодвинуть назад 'z' и 'x' . (Да, я знаю, что sscanf () , который я использовал здесь для простого тестирования, не работает с потоком, но я твердо предполагаю, что они сделали все функции ... scanf () ведут себя одинаково для согласованности.)

Итак ... one-char ungetc () на самом деле не является причиной, здесь ...?: - /

Да, результаты расходятся . Я до сих пор не могу это объяснить как следует ...: - (

не работает с потоком, но я твердо уверен, что они заставили все функции ... scanf () вести себя одинаково для согласованности.)

Итак ... one-char ungetc () не является причиной, здесь ...?: - /

Да, результаты различаются . Я до сих пор не могу это объяснить как следует ...: - (

не работает с потоком, но я твердо уверен, что они заставили все функции ... scanf () вести себя одинаково для согласованности.)

Итак ... one-char ungetc () не является причиной, здесь ...?: - /

Да, результаты различаются . Я до сих пор не могу объяснить это как следует ...: - (

0
ответ дан 2 December 2019 в 21:23
поделиться

Для ввода в функции scanf () , а также для функций strtol () , в сек. . 7.20.1. 4 P7 указывает: Если предметная последовательность пуста или не имеет ожидаемой формы, преобразование не выполняется; значение nptr сохраняется в объекте, на который указывает endptr, при условии, что endptr не является нулевым указателем . Также вы должны учитывать, что правила синтаксического анализа тех токенов, которые определены в соответствии с правилами Sec. 6.4.4 Константы , правило, указанное в разд. 7.20.1.4 P5 .

Остальное поведение, такое как значение errno , должно зависеть от реализации. Например, в моем ящике FreeBSD я получил значения EINVAL и ERANGE , и в Linux происходит то же самое, где стандартные рефереры только на значение errno ERANGE .

] преобразование не выполняется; значение nptr сохраняется в объекте, на который указывает endptr, при условии, что endptr не является нулевым указателем . Также вы должны учитывать, что правила синтаксического анализа тех токенов, которые определены в соответствии с правилами Sec. 6.4.4 Константы , правило, указанное в разд. 7.20.1.4 P5 .

Остальное поведение, такое как значение errno , должно зависеть от реализации. Например, в моем ящике FreeBSD я получил значения EINVAL и ERANGE , и в Linux происходит то же самое, где стандартные рефереры только на значение errno ERANGE .

] преобразование не выполняется; значение nptr сохраняется в объекте, на который указывает endptr, при условии, что endptr не является нулевым указателем . Также вы должны учитывать, что правила синтаксического анализа тех токенов, которые определены в соответствии с правилами Sec. 6.4.4 Константы , правило, указанное в разд. 7.20.1.4 P5 .

Остальное поведение, такое как значение errno , должно зависеть от реализации. Например, в моем ящике FreeBSD я получил значения EINVAL и ERANGE , и в Linux происходит то же самое, где стандартные рефереры только на значение errno ERANGE .

] Также вы должны учитывать, что правила синтаксического анализа тех токенов, которые определены в соответствии с правилами Sec. 6.4.4 Константы , правило, указанное в разд. 7.20.1.4 P5 .

Остальное поведение, такое как значение errno , должно зависеть от реализации. Например, в моем ящике FreeBSD я получил значения EINVAL и ERANGE , и в Linux происходит то же самое, где стандартные рефереры только на значение errno ERANGE .

] Также вы должны учитывать, что правила синтаксического анализа тех токенов, которые определены в соответствии с правилами Sec. 6.4.4 Константы , правило, указанное в разд. 7.20.1.4 P5 .

Остальное поведение, такое как значение errno , должно зависеть от реализации. Например, в моем ящике FreeBSD я получил значения EINVAL и ERANGE , и в Linux происходит то же самое, где стандартные рефереры только на значение errno ERANGE .

]
0
ответ дан 2 December 2019 в 21:23
поделиться

To summarize what should happen according to the standard when parsing numbers:

  • if fscanf() succeeds, the result must be identical to the one obtained via strto*()
  • in contrast to strto*(), fscanf() fails if

    the longest sequence of input characters [...] which is, or is a prefix of, a matching input sequence

    according to the definition of fscanf() is not

    the longest initial subsequence [...] that is of the expected form

    according to the definition of strto*()

This is somewhat ugly, but a necessary consequence of the requirement that fscanf() should be greedy, but can't push back more than one character.

Some library implementators opted for differing behaviour. In my opinion

  • letting strto*() fail to make results consistent is stupid (bad mingw)
  • pushing back more than one character so fscanf() accepts all values accepted by strto*() violates the standard, but is justified (hurray for newlib if they didn't botch strto*() :()
  • not pushing back the non-matching characters but still only parsing the ones of 'expected form' seems dubious as characters vanish into thin air (bad glibc)
1
ответ дан 2 December 2019 в 21:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: