Передача нулевого байта через спецификатор формата в `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 '. Если строковый литерал имеет встроенные нулевые символы, он представляет массив, содержащий более одной строки.