Разве спецификатор %[] или %[^] в функциях scanf(), sscanf() или fscanf() не сохраняет ввод в массив символов с нулевым символом в конце?

Вот что содержит руководство Beez C (ССЫЛКА) сообщает о спецификаторе формата %[]:

It allows you to specify a set of characters to be stored away (likely in an array of chars). Conversion stops when a character that is not in the set is matched.

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

1) Являются ли данные, полученные этими двумя спецификаторами формата, сохраненными в аргументах (типа char*) в виде массива символов или массива символов с завершающим символом \0 (строка)? Если это не строка, как сохранить ее как строку в случаях, подобных приведенной ниже программе, где мы хотим получить последовательность символов в виде строки и остановиться, когда встречается определенный символ (в наборе символов с отрицанием)?

2) Моя программа, по-видимому, предполагает, что обработка для спецификатора %[^|] останавливается, когда встречается отрицательный символ |. Но когда она начинается снова для следующего спецификатора формата, начинается ли она с инвертированного символа, где он остановился раньше? В моей программе я намерен игнорировать |, поэтому я использовал %*c. Но я проверил и обнаружил, что если я использую %c и дополнительный аргумент типа char, то символ | действительно сохраняется в этом аргументе.

3) И, наконец, что очень важно для меня, в чем разница между передачей массива символов для спецификатора формата %s в printf() и строкой (массив символов с нулевым завершением)? В моей другой программе под названием character array vs string я передали массив символов (не завершающийся NULL) для спецификатора формата %s в printf(), и он будет напечатан так же, как строка. В чем разница?

//Программа для иллюстрации спецификатора %[^]

#include<stdio.h>

int main()
{
char *ptr="fruit|apple|lemon",type[10],fruit1[10],fruit2[10];

sscanf(ptr, "%[^|]%*c%[^|]%*c%s", type,fruit1, fruit2);
printf("%s,%s,%s",type,fruit1,fruit2);
}

//массив символов и строка

#include<stdio.h>

int main()
{
char test[10]={'J','O','N'};
printf("%s",test);
}

Вывод JON

//Используем %c вместо %*c

#include<stdio.h>

int main()
{
char *ptr="fruit|apple|lemon",type[10],fruit1[10],fruit2[10],char_var;

sscanf(ptr, "%[^|]%c%[^|]%*c%s", type,&char_var,fruit1, fruit2);
printf("%s,%s,%s,and the character is %c",type,fruit1,fruit2,char_var);

}

Вывод fruit,apple,lemon,and the character is |


person Rüppell's Vulture    schedule 09.05.2013    source источник


Ответы (2)


  1. #P1# <блочная цитата> #P2#
  2. Исключенные символы не используются набором сканирования и остаются для обработки. Альтернативный спецификатор формата:

    if (sscanf(ptr, "%9[^|]|%9[^|]|%9s", type,fruit1, fruit2) == 3)
    
  3. Массив на самом деле завершается нулем, так как остальные элементы будут инициализированы нулем:

    char test[10]={'J','O','N' /*,0,0,0,0,0,0,0*/ };
    

Если бы он не завершался нулем, он продолжал бы печатать до тех пор, пока где-то в памяти не был бы найден нулевой символ, что, возможно, превысило бы конец массива, что привело бы к неопределенному поведению. Можно напечатать массив с нулевым завершением:

    char buf[] = { 'a', 'b', 'c' };
    printf("%.*s", 3, buf);
person hmjd    schedule 09.05.2013
comment
Можете ли вы четко указать, что в другом ответе утверждение Тони о второй части неверно, когда он утверждает, что %*c не должен потреблять исключенный символ? - person Rüppell's Vulture; 09.05.2013
comment
Я интуитивно согласен с вами, что исключенный персонаж должен остаться для обработки, но ведь Тони тоже ветеран и его нельзя просто так игнорировать.... - person Rüppell's Vulture; 09.05.2013
comment
@Rüppell'sVulture, он не обрабатывается набором сканирования. - person hmjd; 09.05.2013
comment
и остается открытым для обработки следующим спецификатором формата, который в моем случае %*c или %c? - person Rüppell's Vulture; 09.05.2013
comment
Что делает ваш альтернативный спецификатор формата для 2)? Он указывает только, что спецификатор формата должен извлекать максимум 9 символов. Но зачем его использовать, если мы знаем, что он все равно остановится на символе с отрицанием? - person Rüppell's Vulture; 09.05.2013
comment
Да, для обработки следующей частью спецификатора формата. - person hmjd; 09.05.2013
comment
Чтобы предотвратить переполнение буфера. Здесь это может выглядеть безответно, но что, если ввод был из внешнего источника. - person hmjd; 09.05.2013
comment
И, наконец, что вы скажете на ответ Тони на 3), что он не определен? - person Rüppell's Vulture; 09.05.2013
comment
Обновлен мой ответ, который поясняет, что имел в виду Тони. - person hmjd; 09.05.2013
comment
+1 от меня :-). Спасибо, что помогли объяснить, как наши ответы соотносятся/различаются. Ваше здоровье. - person Tony Delroy; 09.05.2013

1) Получен ли ввод этими двумя спецификаторами формата, хранящимися в аргументах (типа char *) в виде массива символов или массива символов с завершающим символом \0 (строка)? Если это не строка, как сохранить ее как строку в случаях, подобных приведенной ниже программе, где мы хотим получить последовательность символов в виде строки и остановиться, когда встречается определенный символ (в наборе символов с отрицанием)?

Они хранятся в формате ASCIIZ с разделителем NUL/'\0'.

2) Моя программа, кажется, предполагает, что обработка останавливается для спецификатора %[^|], когда отрицательный символ | встречается. Но когда он запускается снова для следующего спецификатора формата, начинается ли он с инвертированного символа, на котором он остановился ранее? В моей программе я намерен игнорировать | поэтому я использовал %*c. Но я проверил и обнаружил, что если я использую %c и дополнительный аргумент типа char, то символ | действительно хранится в этом аргументе.

Он не должен потреблять следующий символ. Покажите нам свой код или этого не произошло ;-P.

3) И, наконец, что очень важно для меня, в чем разница между передачей массива символов для спецификатора формата %s в printf() и строки (массив символов с нулевым завершением)? В моей другой программе под названием массив символов против строки я' Мы передали массив символов (не заканчивающийся NULL) для спецификатора формата %s в printf(), и он будет напечатан так же, как и строка. В чем разница?

(изменить: следующее касается вопроса выше, в котором говорится о поведении массива в целом и шире, чем фрагмент кода в вопросе, который конкретно поставил случай char[10] = "abcd"; и является безопасным)

%s должен быть передан указатель на текст ASCIIZ ... даже если этот текст явно находится в массиве символов, обязательное присутствие терминатора NUL определяет текстовое содержимое, а не длину массива. Вы должны NUL завершать свой массив символов, иначе у вас будет неопределенное поведение. Иногда вам это может сойти с рук - например. strncpy в массив завершит его NUL тогда и только тогда, когда для этого есть место, а статические массивы начинаются с всего 0 содержимого, поэтому, если вы перезапишете только перед последним символом, у вас будет NUL, ваш char[10 ] имеет элементы, для которых не указаны значения, заполненные NUL, но обычно вы должны взять на себя ответственность за то, чтобы что-то гарантировало завершение NUL.

person Tony Delroy    schedule 09.05.2013
comment
Я добавил код. Проверьте его. Он работает так, как я сказал. Теперь у меня есть два ответа для второй части, один от вас и один от hmjd, и они противоречат друг другу. Вы говорите это shouldn't потреблять, он говорит, что да. - person Rüppell's Vulture; 09.05.2013
comment
@Rüppell'sVulture, я сказал несъеденный. - person hmjd; 09.05.2013
comment
Вы сказали It shouldn't consume the next character. - person Rüppell's Vulture; 09.05.2013
comment
Ваш ответ 3) также конфликтует с hmjd. Он ясно заявляет, что символ завершается нулем из-за самой природы инициализации. Таким образом, возникает вопрос о < b>UB по его словам. Что бы вы сказали? - person Rüppell's Vulture; 09.05.2013
comment
Для char x[10] = "123456789"; вам гарантирован NUL, но он хрупкий — если вы просчитались и, скажем, char x[10] = "1234567890", нет неуказанных завершающих элементов для заполнения NUL. Лучше всего использовать char x[] = "whatever";, если вам нужен только один NUL и вы не будете записывать больше данных в буфер, или если вы читаете данные в буфер, иногда вам понадобится дополнительный x[9] = '\0'; для обеспечения завершения. Важно отметить, что если у вас есть char x[10]; x[0] = 'A';, он не будет прекращен автоматически. - person Tony Delroy; 09.05.2013
comment
Вам также следует рассмотреть возможность использования спецификаций максимальной ширины в ваших scanf преобразованиях. Вам также следует рассмотреть возможность использования спецификаций максимальной ширины в ваших scanf преобразованиях, чтобы гарантировать, что вы не записываете данные за пределы предоставленного буфера. - person Tony Delroy; 09.05.2013
comment
И повторное потребление... Я говорю, что "%[^|]" не будет потреблять '|'; следующий %*c потребляет его. Вы утверждаете, что ваш тест этого не видел... Я говорю, что вы неправильно закодировали или неверно истолковали свой тест. - person Tony Delroy; 09.05.2013
comment
@TonyD Я не получал уведомления о новых сообщениях о ваших новых комментариях, как ожидается. Не знаю, почему иногда это не происходит. Я был в сети так долго, но только сейчас увидел ваши комментарии. - person Rüppell's Vulture; 09.05.2013
comment
@Rüppell'sVulture: о, странно, в будущем я буду использовать явный @.... Ваше здоровье. - person Tony Delroy; 09.05.2013
comment
@TonyD Нет, проблема не в этом. Поскольку я отвечаю за вопрос, любая активность на нем должна меня насторожить. Обычно это так, но иногда не получается. - person Rüppell's Vulture; 09.05.2013