Поскольку у нас есть snprintf, почему у нас нет snscanf?

У меня есть snprintf, и он может избежать переполнения буфера, но почему нет функции с именем snscanf?

код:

int main()
{
     char * src = "helloeveryone";
     char buf1[5];
     sscanf(src,"%s",buf1); // here is a  array out of bounds

}

Итак, я думаю, нужен и snscanf. Почему у нас есть только snprintf?

Ответ 1

В противоречивом (и необязательном) приложении K на C11 добавлена ​​функция sscanf_s, которая после аргумента указателя вводит дополнительный аргумент типа rsize_t (также определенный в приложении K), определяя размер массива с указателем, К лучшему или худшему, эти функции широко не поддерживаются. Вы можете достичь тех же результатов, поместив размер в спецификатор преобразования, например.

char out[20];
sscanf(in, "%19s", out);

но это неудобно и подвержено ошибкам, если размер целевого объекта может меняться во время выполнения (вам нужно будет программно разработать спецификатор преобразования с помощью snprintf). Обратите внимание, что ширина поля в спецификаторе преобразования - это максимальное количество вводимых символов для чтения, а sscanf также записывает завершающий нулевой байт для преобразований %s, поэтому пройденная ширина поля должна быть строго меньше, чем размер объект назначения.

Ответ 2

Нет необходимости в snscanf(), потому что нет записи первого аргумента буфера. Длина буфера в snprintf() указывает размер буфера, в котором идет запись:

char buffer[256];

snprintf(buffer, sizeof(buffer), "%s:%d", s, n);

Буфер в соответствующей позиции для sscanf() - строка с нулевым символом; нет необходимости в явной длине, так как вы не собираетесь писать ей (это a const char * restrict buffer в C99 и C11).

char buffer[256];
char string[100];
int n;
if (sscanf(buffer, "%s %d", string, &n) != 2)
    ...oops...

На выходе вы уже должны указывать длину строк (хотя вы, вероятно, большинство, если используете %s, а не %99s или что-то строгое):

if (sscanf(buffer, "%99s %d", string, &n) != 2)
    ...oops...

Было бы неплохо/полезно, если бы вы могли использовать %*s, как вы можете, с помощью snprintf(), но вы не можете - в sscanf(), * означает "не назначать сканированное значение", а не длина. Обратите внимание, что вы не будете писать snscanf(src, sizeof(buf1), "%s", buf1), не в последнюю очередь потому, что вы можете иметь несколько спецификаций преобразования %s в одном вызове. Написание snscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2) не имеет смысла, не в последнюю очередь потому, что оно оставляет неразрешимую проблему при разборе списка varargs. Было бы неплохо иметь такую ​​нотацию, как snscanf(src, "%@s %@s", sizeof(buf1), buf1, sizeof(buf2), buf2), чтобы избежать необходимости указывать размер поля (минус один) в строке формата. К сожалению, вы не можете сделать это с помощью sscanf() et al.

Приложение K ISO/IEC 9899: 2011 (ранее TR24731) предоставляет sscanf_s(), который принимает длину для символьных строк и может использоваться как:

if (sscanf_s(buffer, "%s %d", string, sizeof(string), &n) != 2)
    ...oops...

(Благодаря R.., чтобы напомнить мне об этом теоретическом варианте - теоретически, потому что только Microsoft реализовала "безопасные" функции, и они не реализовали их точно как требует стандарт.)

Обратите внимание, что §K.3.3 Общие определения <stddef.h> говорит: '... Тип rsize_t, который является типом size_t. 385) '(и в сноске 385 говорится: "См. описание макроса RSIZE_MAX в <stdint.h>.". Это означает, что на самом деле вы можете передать size_t без необходимости приведения в действие - пока переданное значение находится в пределах диапазона, определенного RSIZE_MAX in <stdint.h>. (Общее намерение состоит в том, что RSIZE_MAX является довольно большим числом, но меньше SIZE_MAX. Для получения дополнительной информации прочитайте стандарт 2011 года или получите TR 24731 из Открытый стандарт.

Ответ 3

В sscanf(s, format, ...) массив проверенных символов - const char *. В s нет записи. Сканирование останавливается, когда s[i] равно NUL. Мало необходимости в параметре n в качестве дополнительного предела сканирования.

В sprintf(s, format, ...) массив s является пунктом назначения. snprintf(s, n, format, ...) гарантирует, что данные не привязаны к s[n] и далее.


Что было бы полезно, так это расширение флага для спецификаторов преобразования sscanf(), поэтому ограничение может быть легко указано во время компиляции. (Сегодня это можно сделать громоздким способом, с динамическим форматом или с sscanf(src,"%4s",buf1).)

// This is a proposed idea for C. - Not valid code today.
sscanf(src, "%!s", sizeof(buf1), buf)

Здесь ! сообщит sscanf() прочитать переменную size_t для ограничения размера предстоящей строки. Может быть, в C17?


Громоздкий метод, который работает сегодня.

char * src = "helloeveryone";
char buf1[5];
char format[1+20+1+1];
sprintf(format, "%%" "%zu" "s", sizeof(buf1) - 1);
sscanf(src, format, buf1);

Ответ 4

Почему бы вам не попробовать fgets() (со стандартным входным файлом stdin)?

fgets() позволяет указать максимальный размер для вашего буфера.

(В целом, я буду использовать стандартный ISO C99 совместимый Синтаксис.)

Таким образом, вы можете написать этот код:

#include <stdio.h>
#define MAXBUFF 20 /* Small just for testing... */
int main(void) {
  char buffer[MAXBUFF+1]; /* Add 1 byte since fgets() inserts '\0' at end */
  fgets(buffer, MAXBUFF+1, stdin);
  printf("Your input was: %s\n", buffer);
  return 0;
}

fgets() читает не более MAXBUFF символов из stdin,
который является стандартным входом (это означает: клавиатура).
Результат сохраняется в массиве buffer.
Если найден символ '\n', чтение останавливается и '\n' также сохраняется в buffer (в качестве последнего символа). Кроме того, всегда добавляется '\ 0' в конце buffer, поэтому требуется достаточное количество хранилища.
Вы можете использовать комбинацию fgets(), а затем sscanf() для обработки строки:

  char buffer[MAXBUFF+1];
  fgets(buffer, MAXBUFF+1, stdin); /* Plain read */
  int x; float f;
  sscanf(buffer, "%d %g", &x, &f); /* Specialized read */

Таким образом, у вас есть "безопасный" метод scanf().

Примечание.. Этот подход имеет потенциальную проблему. Если fgets() достигает символов MAXBUFF до получения символа конца строки '\n', остальная часть ввода не будет отбрасываться, и это будет принято как часть следующего чтения клавиатуры.
Следовательно, нужно добавить флеш-механизм, который на самом деле очень прост:

while(getchar()!'\n') 
    ; /* Flushing stdin... */

Однако: если вы просто добавите последний фрагмент кода после строки fgets(),
пользователь будет вынужден два раза нажать ВВОД два раза каждый раз, когда он вводит меньше символов MAXBUFF. Хуже всего: это самая типичная ситуация!

Чтобы исправить эту новую проблему, обратите внимание на то, что простое логическое условие полно, эквивалентное тому, что символ '\n' не был достигнут, выглядит следующим образом:

(buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n')

(Докажите это!)

Таким образом, мы пишем:

fgets(buffer, maxb+1, stdin);
if ((buffer[MAXBUFF - 1] != '\0') && (buffer[MAXBUFF - 1] != '\n'))
     while(getchar() != '\n')
       ;

Последний штрих необходим: поскольку буфер массива может иметь garbadge,
кажется, что нужна какая-то инициализация.
Однако заметим, что нужно очистить только положение [MAXBUFF - 1]:

char buffer[MAXBUFF + 1] = { [MAXBUFF - 1] = '\0' }; /* ISO C99 syntax */

Наконец, мы можем собрать все эти факты в быстром макросе, как показано в этой программе:

#include <stdio.h>
#define safe_scanf(fmt, maxb, ...) { \
    char buffer[maxb+1] = { [maxb - 1] = '\0' }; \
    fgets(buffer, maxb+1, stdin); \
    if ((buffer[maxb - 1] != '\0') && (buffer[maxb - 1] != '\n')) \
        while(getchar() != '\n') \
           ; \
    sscanf(buffer, fmt, __VA_ARGS__); \
  }
#define MAXBUFF 20     

int main(void) {
  int x; float f;      
  safe_scanf("%d %g", MAXBUFF+1, &x, &f);
  printf("Your input was: x == %d\t\t f == %g",  x, f);
  return 0;
}

Для макроса использовался механизм переменной количества параметров. в соответствии со стандартами ISO C99: Макросы Variadic
__VA_ARGS__ заменяет список переменных параметров.
(Нам нужно переменное количество параметров, чтобы имитировать поведение scanf().)

Примечания: Макро-тело было заключено внутри блока с {}. Это не совсем удовлетворительно, и его легко улучшить, но он является частью другой темы...
В частности, макрос safe_scanf() не возвращает значение (это не выражение, а блок).

Примечание: Внутри макроса я объявил массив buffer, который был создан во время ввода блока, а затем уничтожается при выходе из блока. Объем buffer ограничен блоком макроса.

Ответ 6

немного больше морщин. "n" обычно относится к первому аргументу в snprintf. Теперь верно, что первый строковый аргумент в sscanf не записывается. Однако он читается. Таким образом, следующее может быть segfault:

char s[2];
s[0]='1'; s[1]='3';
int x;
sscanf(s, "%d", &x);

потому что шаг 1 char за пределами s может непреднамеренно перейти на чтение из памяти undefined (или продолжить целое число из другой переменной). так что что-то вроде этого было бы полезно:

 snscanf(s, 2, "%d", &x);

s не является строкой, конечно, но это массив символов. "n" в snscanf предотвратит переполнение (чтение) первого аргумента (исходной строки) и не будет связано с аргументом назначения.

способ избежать этого - сначала убедиться, что s завершается символом '\ 0' в пределах 2 символов. вы не можете использовать strlen, конечно. вам нужно strnlen и тест, если он меньше 2. если он равен 2, то сначала потребуется больше усилий по копированию.