Каково поведение печати NULL с помощью спецификатора %s printf?
наткнулся на интересный вопрос интервью:
test 1:
printf("test %sn", NULL);
printf("test %sn", NULL);
prints:
test (null)
test (null)
test 2:
printf("%sn", NULL);
printf("%sn", NULL);
prints
Segmentation fault (core dumped)
хотя это может работать на некоторых системах, по крайней мере мой бросает ошибку сегментации. Как лучше всего объяснить такое поведение? Выше код находится в с.
Ниже приведена моя информация о gcc:
deep@deep:~$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
4 ответов
перво-наперво: printf
ожидает допустимого (т. е. ненулевого)
указатель для его аргумента %s, поэтому передача ему NULL официально
не определено. Он может печатать "(null) " или удалять все файлы на вашем
жесткий диск--либо правильное поведение, насколько ANSI обеспокоен
(по крайней мере, так говорят Харбисон и Стил.)
это, как говорится, Да, это действительно странное поведение. Оказывается
вот что происходит, когда вы делаете простой printf
как это:
printf("%s\n", NULL);
gcc is (кхм) достаточно умный, чтобы деконструировать это в вызов
puts
. Первый printf
это:
printf("test %s\n", NULL);
достаточно сложно, что gcc вместо этого будет выдавать вызов real
printf
.
(обратите внимание, что gcc выдает предупреждения о вашем недопустимом и затем просматривая полученный .
когда я скомпилировал первый пример, я получил:
movl $.LC0, %eax
movl , %esi
movq %rax, %rdi
movl , %eax
call printf ; <-- Actually calls printf!
(комментарии добавлены мной.)
но второй произвел этот код:
movl , %edi ; Stores NULL in the puts argument list
call puts ; Calls puts
самое странное, что он не печатает следующую новую строку. Это как если бы он понял, что это вызовет segfault так что это не беспокоит. (Что он и сделал-он предупредил меня, когда я составленный он.)
что касается языка C, причина в том, что вы вызываете неопределенное поведение, и все может произойти.
что касается механики, почему это происходит, современный gcc оптимизирует printf("%s\n", x)
до puts(x)
и puts
не имеет глупого кода для печати (null)
когда он видит нулевой указатель, тогда как общие реализации printf
этот особый случай. Поскольку gcc не может оптимизировать (в целом) нетривиальные строки формата, как это,printf
на самом деле вызывается когда в строке формата присутствует другой текст.
в разделе 7.1.4 (C99 или C11) говорится:
§7.1.4 использование библиотечных функций
¶1 Каждый из следующих операторов применяется, если явно не указано иное в подробном описаниях: если аргумент функции имеет неверное значение (например, значение вне домена функции или указателя вне адресного пространства программы, или нулевой указатель, или указатель на немодифицируемое хранилище, когда соответствующий параметр не является const-квалифицированным) или типом (после продвижения), не ожидаемым функцией с переменным числом аргументов поведение не определено.
С указанием printf()
ничего не говорит о том, что происходит, когда вы передаете указатель на %s
спецификатор, поведение явно не определено. (Обратите внимание, что передача нулевого указателя для печати %p
спецификатор не является неопределенным поведением.)
здесь "глава и стих" для fprintf()
семейное поведение (C2011-это другой номер раздела В C1999):
§7.21.6.1 функция fprintf
s
, если неl
модификатор длины присутствует, аргумент должен быть указателем на начальный элемент массива символьного типа. [...]если
l
модификатор длины присутствует, аргумент должен быть указателем на начальный элемент массива wchar_t тип.
p
аргумент должен быть указателем на Void. Значение указателя преобразуется в последовательность печатных знаков, в реализации манера.
спецификации s
спецификатор преобразования исключает возможность допустимости нулевого указателя, поскольку нулевой указатель не указывает на исходный элемент массива соответствующего типа. Спецификация для p
спецификатор преобразования делает не требуйте, чтобы указатель void указывал на что-либо в частности, и поэтому NULL действителен.
тот факт, что многие реализации печатают строку, такую как (null)
при передаче нулевого указателя-это доброта, что опасно полагаться на. Прелесть неопределенного поведения в том, что такая реакция разрешена, но не требуется. Аналогично, сбой разрешен, но не требуется (больше жалости-люди кусаются, если они работают на прощающей системе, а затем переносятся на другие менее прощающие системы).
на NULL
указатель не указывает на какой-либо адрес, и попытка распечатать его вызывает неопределенное поведение. Неопределенное значение это зависит от вашего компилятора или библиотеки C, чтобы решить, что делать, когда он пытается напечатать NULL.