Передача нулевого байта через спецификатор формата в `printf`
Почему printf
печатает пробел вместо остановки, когда я использую символ NULL из таблицы ASCII? Вот что я имею в виду:
printf("Hello%c, world", 0); //Hello , world
printf("Hello%c, world", '\0'); //Hello , world
Только когда я помещаю escape-символ в самой строке printf
, останавливает строку:
printf("Hello\0, world"); //Hello
Я пробовал это в Windows 8, Windows 10 (используя cygwin, MinGW, Netbeans, Code:: Blocks), XUbuntu, все равно.
Где проблема? Я спросил одного из моих друзей, но он сказал, что у него нет такой проблемы, что все три примера выполняются одинаково.
Ответы
Ответ 1
printf("Hello\0, world");
использует свой параметр как C-строку, поэтому он декодирует его до тех пор, пока не найдет NUL char, поэтому он останавливается сразу после \0
, игнорируя следующее.
printf("Hello%c, world", 0);
декодирует свой параметр (пока он не найдет внутри него NUL char - то есть после d
), тем временем он найдет %c
, поэтому он заменяет его char заданным параметром (код ASCII - NUL), а затем отправить на терминал NUL char, а затем продолжить.
В руководстве Printf говорится:
Эти функции записывают вывод под управлением строки формата который определяет, как последующие аргументы [...] преобразуются для выход.
Ответ 2
Вы зависите от детали реализации printf(). Низкоуровневая функция вывода терминала требует длины строки в качестве аргумента. Для printf() существует два способа:
Несколько очевидный способ - сначала форматировать строку, а затем использовать strlen(). Это то, на что вы надеялись.
Но это неэффективно, потому что для этого требуется двойной проход в буфере строк и добавление 0. Другой способ сделать это - отслеживать длину форматированной строки при подстановке полей, просто увеличивая ее для каждого добавленного символа. Так как он продолжается до% c, теперь вы получите большую длину, которая включает все прошлое% c. То, что делает терминальная функция со встроенным 0, также является деталью реализации, учитывая, что это не печатный символ. Видя, что это замечено пробелом, не редкость.
Разумный способ сделать это - не полагаться на детали реализации.
Ответ 3
printf("Hello%c, world", 0); //Hello , world
printf("Hello%c, world", '\0'); //Hello , world
В обоих случаях вы пытаетесь распечатать значение символа, соответствующее символьному коду 0
, который не является печатным символом. Я не нашел в нем главы и стихи, но я подозреваю, что поведение попытки напечатать значение символа nul не определено или, может быть, даже undefined. В любом случае, я бы не ожидал, что в этом случае он будет рассматриваться как ограничитель строк.
printf("Hello\0, world"); //Hello
В этом случае нулевой символ является частью строковой константы и интерпретируется компилятором в качестве ограничителя строк.
Ответ 4
Вкратце:
%c
означает печать символа, поэтому printf
напечатать символ NUL
, значение которого равно 0. NUL
- это непечатаемые символы. Таким образом, мы можем видеть только пространство.
"Hello\0, world" - строковый литерал, результат strlen("Hello\0, world")
равен 5. Таким образом, printf
напечатает результат "Hello".
Вы можете увидеть больше на веб-сайте cppreference: строковый литерал
Литеральный символ строки представляет собой последовательность из нулевых или более многобайтовых символов, заключенных в двойные кавычки, как в "xyz". Нулевой символ ('\ 0') всегда добавляется к строковому литералу, поэтому строковый литерал "Hello" представляет собой const char [6], где хранятся символы "H", "e", "l", l ',' 0 'и'\0 '. Если строковый литерал имеет встроенные нулевые символы, он представляет массив, содержащий более одной строки.