Разница между scanf () и strtol () / strtod () в числах разбора

Примечание: я полностью переработал вопрос, чтобы более правильно отразить, для чего я устанавливаю награду. Пожалуйста, простите любые несоответствия с уже данными ответами, которые это могло бы создать. Я не хотел создавать новый вопрос, так как предыдущие ответы на этот вопрос могут быть полезны.


Я работаю над реализацией стандартной библиотеки C и запутался в одном конкретном углу стандарта.

стандарт определяет количество форматы, принятые scanf семейство функций (%d, %i, %u, %o, %x) в терминах определений для strtol, strtoul и strtod.

стандарт также говорит, что fscanf() будет возвращать максимум один символ во входной поток, и поэтому некоторые последовательности, принятые strtol, strtoul и strtod недопустимо fscanf (ISO / IEC 9899:1999, сноска 251).

я попытался найти некоторые значения, которые будут показывать такие различия. Получается выходит, что шестнадцатеричный префикс "0x", за которым следует символ, не являющийся шестнадцатеричной цифрой, является одним из таких случаев, когда два семейства функций различаются.

забавно, стало очевидно, что никакие две доступные библиотеки C, похоже, не согласны с выходом. (См. тестовую программу и пример вывода в конце этого вопроса.)

что я хотел бы услышать это что будет считаться стандартным поведением при разборе "0xz"?. В идеале со ссылкой на соответствующие части из стандарта, чтобы сделать точку.

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

int main()
{
    int i, count, rc;
    unsigned u;
    char * endptr = NULL;
    char culprit[] = "0xz";

    /* File I/O to assert fscanf == sscanf */
    FILE * fh = fopen( "testfile", "w+" );
    fprintf( fh, "%s", culprit );
    rewind( fh );

    /* fscanf base 16 */
    u = -1; count = -1;
    rc = fscanf( fh, "%x%n", &u, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %dn", rc, u, count );
    rewind( fh );

    /* strtoul base 16 */
    u = strtoul( culprit, &endptr, 16 );
    printf( "strtoul:             result %2d, consumed %dn", u, endptr - culprit );

    puts( "" );

    /* fscanf base 0 */
    i = -1; count = -1;
    rc = fscanf( fh, "%i%n", &i, &count );
    printf( "fscanf:  Returned %d, result %2d, consumed %dn", rc, i, count );
    rewind( fh );

    /* strtol base 0 */
    i = strtol( culprit, &endptr, 0 );
    printf( "strtoul:             result %2d, consumed %dn", i, endptr - culprit );

    fclose( fh );
    return 0;
}

/* newlib 1.14

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0

fscanf:  Returned 1, result  0, consumed 1
strtoul:             result  0, consumed 0
*/

/* glibc-2.8

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1

fscanf:  Returned 1, result  0, consumed 2
strtoul:             result  0, consumed 1
*/

/* Microsoft MSVC

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 0

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 0
*/

/* IBM AIX

fscanf:  Returned 0, result -1, consumed -1
strtoul:             result  0, consumed 1

fscanf:  Returned 0, result  0, consumed -1
strtoul:             result  0, consumed 1
*/

8 ответов


связь с Фредом Дж. Тидеманом, вице-шаром PL22.11 (ANSI "C"), on comp.станд.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 был первым conv. спецификатор) и оставьте " z " в качестве первого непрочитанного символа во входном потоке.

strtol

определение strtolstrtoul) отличается одним решающим моментом:

последовательность темы определяется как самая длинная начальная подпоследовательность вход строка, начиная с первой символ не-белого пространства,это ожидаемая форма. (7.20.1.4 P4, Курсив мой)

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


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


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

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


чтобы суммировать, что должно произойти в соответствии со стандартом при разборе чисел:

  • если fscanf() удастся, то результат должен быть идентичен полученному через strto*()
  • в отличие от strto*(), fscanf(), Если не

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

    по определению fscanf() is не

    самая длинная начальная подпоследовательность [...] это в ожидаемой форме

    по определению strto*()

это несколько некрасиво, но необходимое следствие требований, что fscanf() должен быть жадным, но не может отодвинуть более одного символа.

некоторые реализации библиотеки выбрали различное поведение. По-моему

  • letting strto*() незачет согласовывать результаты глупо ( bad mingw)
  • отталкивая более одного символа так fscanf() принимает все значения, принятые strto*() нарушает стандарт, но это оправдано (ура для newlib, если они не испортили strto*() : ()
  • не отталкивая несовпадающие символы, но все же только разбор тех из "ожидаемой формы" кажется сомнительным, поскольку символы исчезают в воздухе ( плохой glibc)

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


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

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

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


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

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


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


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

после тестирования всех комбинаций спецификаторов преобразования и входных вариаций, которые я мог придумать, я могу сказать, что правильно, что два семейства функций не дают одинаковых результатов. (По крайней мере в 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' обратно. (Да, я знаю это!--5-->, который я использовал здесь для легкого тестирования, не работает на потоке, но я сильно предполагаю, что они сделали все ...scanf() функции ведут себя одинаково для консистенция.)

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

да результаты отличаются. Но я все еще не могу толком объяснить... :-(