Поскольку у нас есть 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 ?

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

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

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

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

 char buffer[256]; snprintf(buffer, sizeof(buffer), "%s:%d", s, n); 

Буфер в соответствующей позиции для sscanf() – строка с нулевым завершением; нет необходимости в явной длине, так как вы не собираетесь ее писать (это 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) писать snscanf(src, sizeof(buf1), "%s", buf1) , не в последнюю очередь потому, что вы можете иметь несколько спецификаций преобразования %s за один вызов. Написание snscanf(src, sizeof(buf1), sizeof(buf2), "%s %s", buf1, buf2) не имеет смысла, не в последнюю очередь потому, что оно оставляет неразрешимую проблему при parsingе списка 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 Общие определения гласит: ‘… Тип rsize_t который является типом size_t . 385) ‘(и в сноске 385 говорится: «См. Описание макроса RSIZE_MAX в . Это означает, что на самом деле вы можете передать size_t без необходимости приведения в действие – пока переданное значение находится в пределах определенного диапазона по RSIZE_MAX в . (Общее намерение состоит в том, что RSIZE_MAX является довольно RSIZE_MAX числом, но меньше, чем SIZE_MAX . Подробнее см. стандарт 2011 года или получите TR 24731 с веб-сайта Open Standards .)

В sscanf(s, format, ...) массив проверенных символов представляет собой const char * . Письмо не написано. Сканирование останавливается, когда 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); 

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

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

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

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

 #include  #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 (в качестве последнего символа). Кроме того, всегда в конце buffer добавляется ‘\ 0’, поэтому требуется достаточное количество хранилища.
Вы можете использовать комбинацию 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... */ в while(getchar()!'\n') ; /* Flushing stdin... */ 

Однако: если вы просто добавите последний fragment кода после строки fgets()
пользователь будет вынужден два раза нажать ENTER два раза каждый раз, когда он вводит меньше символов 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  #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\tf == %g", x, f); return 0; } 

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

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

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

Как правильно использовать sscanf

Обратите внимание, что fnprintf не является одним, и большинство функций массива имеют безопасное изменение.

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

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

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

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

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

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