Подтвердить что ты не робот

Каково поведение печати NULL с помощью спецификатора printf% s?

Нашел интересный вопрос интервью:

test 1:
printf("test %s\n", NULL);
printf("test %s\n", NULL);

prints:
test (null)
test (null)

test 2:
printf("%s\n", NULL);
printf("%s\n", NULL);
prints
Segmentation fault (core dumped)

Хотя это может работать нормально на некоторых системах, по крайней мере, моя бросает ошибку сегментации. Каким будет лучшее объяснение этого поведения? Над кодом находится C.

Ниже приведена информация о gcc:

[email protected]:~$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
4b9b3361

Ответ 1

Прежде всего: printf ожидает действительный (то есть не-NULL) указатель для аргумента% s, поэтому передача его NULL официально undefined. Он может печатать "(null)" или может удалять все файлы на вашем жесткий диск - либо правильное поведение, что и ANSI (по крайней мере, то, что говорит мне Харбисон и Стил).

Это, как говорится, да, это действительно странное поведение. Получается что происходит, когда вы делаете простой printf следующим образом:

printf("%s\n", NULL);

gcc (гм) достаточно умен, чтобы деконструировать это в призыв к puts. Первый printf, это:

printf("test %s\n", NULL);

достаточно сложна, что gcc вместо этого выдает вызов реальному printf.

(Обратите внимание, что gcc генерирует предупреждения о неверном аргументе printf когда вы скомпилируете. Это потому, что он давно развил способность parse *printf.)

Вы можете сами убедиться, компилируя с помощью опции -save-temps и затем просматривает результирующий файл .s.

Когда я скомпилировал первый пример, я получил:

movl    $.LC0, %eax
movl    $0, %esi
movq    %rax, %rdi
movl    $0, %eax
call    printf      ; <-- Actually calls printf!

(Комментарии были добавлены мной.)

Но второй создал этот код:

movl    $0, %edi    ; Stores NULL in the puts argument list
call    puts        ; Calls puts

Самое странное, что он не печатает следующую новую строку. Как будто он понял, что это вызовет segfault так что это не беспокоит. (Что у него есть - он предупредил меня, когда я скомпилировал она.)

Ответ 2

Что касается языка C, причина в том, что вы вызываете поведение undefined, и все может случиться.

Что касается механики, почему это происходит, то gcc оптимизирует printf("%s\n", x) до puts(x), а puts не имеет глупого кода для печати (null), когда видит нулевой указатель, тогда как общие реализации printf имеют этот частный случай. Поскольку gcc не может оптимизировать (в общем) нетривиальные строки формата, подобные этому, printf фактически вызывается, когда в строке формата присутствует другой текст.

Ответ 3

В разделе 7.1.4 (C99 или C11) говорится:

§7.1.4 Использование библиотечных функций

¶1 Каждое из следующих утверждений применяется, если явно не указано иное в подробном следующие описания: если аргумент функции имеет недопустимое значение (например, значение вне области функции или указатель вне адресного пространства программы, или нулевой указатель, или указатель на немодифицируемое хранилище, когда соответствующий параметр не является константным) или тип (после продвижения), не ожидаемый функцией с переменным числом аргументов, поведение undefined.

Поскольку спецификация printf() ничего не говорит о том, что происходит, когда вы передаете ему нулевой указатель для спецификатора %s, поведение явно указано undefined. (Обратите внимание, что передача нулевого указателя для печати спецификатором %p не является undefined.)

Вот глава и стих для поведения семейства fprintf() (C2011 - это другой номер раздела в C1999):

§7.21.6.1 Функция fprintf

s   Если нет модификатора длины l, аргумент должен быть указателем на начальную элемент массива типа символа. [...]

    Если присутствует модификатор длины l, аргумент должен быть указателем на начальную элемент массива типа wchar_t.

p   Аргумент должен быть указателем на void. Значение указателя преобразованный в последовательность печатных символов, в определенном реализацией образом.

Спецификации спецификатора преобразования s исключают возможность того, что нулевой указатель действителен, поскольку нулевой указатель не указывает на начальный элемент массива соответствующего типа. Спецификация спецификатора преобразования p не требует, чтобы указатель void указывал на что-либо в частности, и поэтому допустим NULL.

Тот факт, что многие реализации печатают строку, такую ​​как (null) при передаче нулевого указателя, - это доброта, на которую опасно полагаться. Красота поведения undefined заключается в том, что такой ответ разрешен, но он не требуется. Точно так же разрешается крах, но не требуется (больше жаль - люди укусаются, если они работают на прощающей системе, а затем переносят в другие менее прощающие системы).

Ответ 4

Указатель NULL не указывает на какой-либо адрес, а попытка его печати вызывает поведение undefined. undefined означает, что это зависит от вашего компилятора или библиотеки C, чтобы решить, что делать, когда он пытается распечатать NULL.