Печать нулевых указателей с% p является неопределенным поведением?

Является ли неопределенным поведением печать нулевых указателей со спецификатором преобразования %p?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

Вопрос относится к стандарту C, а не к реализациям C.


person Dror K.    schedule 09.07.2017    source источник
comment
На самом деле не думайте, что кто-то (включая комитет C) слишком заботится об этом. Это довольно искусственная проблема, не имеющая (или почти не имеющая) практического значения.   -  person 0___________    schedule 09.07.2017
comment
это так, как printf только отображает значение и не касается (в смысле чтения или записи указанного объекта) - не может быть указатель UB i имеет допустимое значение для своего типа (NULL является допустимым ценить)   -  person 0___________    schedule 09.07.2017
comment
@PeterJ скажем, что то, что вы говорите, верно (хотя в стандарте явно указано иное), только тот факт, что мы обсуждаем это, делает вопрос действительным и правильным, поскольку похоже, что приведенная ниже часть стандарта делает Обычному разработчику очень сложно понять, что, черт возьми, происходит ... Значение: вопрос не заслуживает голоса "против", потому что эта проблема требует разъяснения!   -  person Peter Varo    schedule 09.07.2017
comment
По теме: stackoverflow.com/q/10461360/694576   -  person alk    schedule 09.07.2017
comment
Нет - стандартное чтение% p не является операцией с указателем и было добавлено только по той причине, что размер указателя может отличаться от целочисленного размера. Это исключение из всех правил указателя.   -  person 0___________    schedule 09.07.2017
comment
@PeterJ, тогда это другая история, спасибо за разъяснения :)   -  person Peter Varo    schedule 09.07.2017
comment
GCC 5.4 в Ubuntu 16.04 показывает: (nil), который относится к первой ячейке памяти. Я печатаю p + 1 show: 0x1   -  person EsmaeelE    schedule 11.07.2017
comment
@DrorK. согласно документу (1) Аргумент должен быть указателем на void. Значение указателя преобразуется в последовательность печатаемых символов способом, определяемым реализацией. Если спецификация преобразования недействительна, поведение не определено. Если какой-либо аргумент не является правильным типом для соответствующей спецификации преобразования, поведение не определено.   -  person Ramankingdom    schedule 18.07.2017
comment
см. ниже ссылку на ответ на этот вопрос - заголовок stackoverflow.com/questions/10461360/   -  person Rameshwar Vyevhare    schedule 18.07.2017


Ответы (3)


Это один из тех странных случаев, когда мы сталкиваемся с ограничениями английского языка и несогласованной структурой в стандарте. Так что в лучшем случае я могу привести убедительный контраргумент, поскольку это невозможно доказать :) 1


Код в вопросе демонстрирует четко определенное поведение.

Поскольку в основе вопроса лежит [7.1.4], давайте начнем с этого:

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

Это корявый язык. Одна интерпретация состоит в том, что элементы в списке являются UB для всех функций библиотеки, если они не отменены отдельными описаниями. Но список начинается с «таких как», что указывает на то, что он иллюстративный, а не исчерпывающий. Например, в нем не упоминается правильное завершение строк нулевым символом (критично для поведения, например, strcpy).

Таким образом, ясно, что цель / область действия 7.1.4 просто состоит в том, что «недопустимое значение» ведет к UB (, если не указано иное). Мы должны изучить описание каждой функции, чтобы определить, что считается «недопустимым значением».

Пример 1 - strcpy

[7.21.2.3] говорит только следующее:

Функция strcpy копирует строку, на которую указывает s2 (включая завершающий нулевой символ), в массив, на который указывает s1. Если копирование происходит между перекрывающимися объектами, поведение не определено.

В нем нет явного упоминания нулевых указателей, но и нет упоминания о нулевых терминаторах. Вместо этого из «строки, на которую указывает s2» делается вывод, что единственными допустимыми значениями являются строки (т. Е. Указатели на массивы символов с завершающим нулем).

Действительно, эту закономерность можно увидеть в отдельных описаниях. Еще несколько примеров:

  • # P11 #
  • # P12 #
  • # P13 #

Пример 2 - printf

[7.19.6.1] говорит следующее о %p:

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

Null - это допустимое значение указателя, и в этом разделе не упоминается явно ни то, что null - это особый случай, ни то, что указатель должен указывать на объект. Таким образом определяется поведение.


1. Если не будет заявлен автор стандартов или если мы не найдем что-то похожее на обоснование, разъясняющий ситуацию.

person Oliver Charlesworth    schedule 09.07.2017
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был переехал в чат. - person Bhargav Rao; 09.07.2017
comment
тем не менее, в нем не упоминается, что нулевые терминаторы являются слабыми в Примере 1 - strcpy, поскольку в спецификации говорится, что копирует string. строка явно определяется как имеющая нулевой символ. - person chux - Reinstate Monica; 10.07.2017
comment
@chux - Это в некотором роде моя точка зрения - нужно сделать вывод, что действительно / недействительно, из контекста, а не предполагать, что список в 7.1.4 является исчерпывающим. (Однако существование этой части моего ответа имело несколько больше смысла в контексте комментариев, которые с тех пор были удалены, поскольку аргумент strcpy был контрпримером.) - person Oliver Charlesworth; 10.07.2017
comment
@OliverCharlesworth Вы сказали: одна интерпретация состоит в том, что нулевой указатель всегда является UB ... но в том же параграфе §7.1.4 p1 говорится: Каждое из следующих утверждений применяется, если явно не указано иное в подробных описаниях, которые следуют - person Dror K.; 10.07.2017
comment
Суть проблемы в том, как читатель интерпретирует например. Означает ли это некоторые примеры возможных недопустимых значений? Означает ли это, что некоторые примеры всегда недопустимые значения? Для протокола, я придерживаюсь первой интерпретации. - person ninjalj; 10.07.2017
comment
@DrorK. - Да, я сам об этом думал. Вместо того, чтобы связывать себя узлами, пытаясь распутать это, я просто вырезал _1 _ / _ 2_ материал из своего ответа. - person Oliver Charlesworth; 10.07.2017
comment
@ninjalj - Да, согласен. По сути, это то, что я пытаюсь передать в своем ответе здесь, т.е. это примеры типов вещей, которые могут быть недопустимыми значениями. :) - person Oliver Charlesworth; 10.07.2017
comment
@ninjalj Если мы последуем первой интерпретации, которую вы описали, то часть головоломки в §7.24.1 p2 больше не подходит: pointer arguments on such a call shall still have valid values, as described in 7.1.4. - person Dror K.; 10.07.2017
comment
@OliverCharlesworth DR # 217: в C99 функция asctime не указала ожидаемые диапазоны, комитет процитировал §7.1.4 p1 в качестве ответа и не выдал TC для C99. Таким образом, интерпретация, согласно которой §7.1.4 p1 не указывает, что считается недействительным, не согласуется с их ответом и не согласуется с утверждениями в: §7.24.1 p2, §7.22.5 p1, §7.29.4 p2. - person Dror K.; 10.07.2017
comment
@OliverCharlesworth: Честно говоря, комментарии не удалялись, а перемещались в чат. Я не против, чтобы некоторые из них были перемещены, но другие очень подходили для ответа. Но может потребоваться слишком много усилий, чтобы отличить их друг от друга. Тем не менее, ссылка на чат приведена выше. - person too honest for this site; 10.07.2017

Краткий ответ

Да. Печать нулевых указателей со спецификатором преобразования %p имеет неопределенное поведение. Сказав это, я не знаю ни одной существующей соответствующей реализации, которая могла бы вести себя неправильно.

Ответ относится к любому из стандартов C (C89 / C99 / C11).


Длинный ответ

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

Во введении к функциям стандартной библиотеки говорится, что нулевые указатели в качестве аргументов функций (стандартной библиотеки) считаются недопустимыми значениями, если явно не указано иное.

C99 / C11 §7.1.4 p1

[...] Если аргумент функции имеет недопустимое значение (например, [...] нулевой указатель, [...] поведение не определено.

Примеры для функций (стандартной библиотеки), которые ожидают нулевые указатели как допустимые аргументы:

  • fflush() использует нулевой указатель для очистки «всех потоков» (которые применяются).
  • freopen() использует нулевой указатель для указания файла, «связанного в данный момент» с потоком.
  • snprintf() позволяет передавать нулевой указатель, когда n равно нулю.
  • realloc() использует нулевой указатель для выделения нового объекта.
  • free() позволяет передавать нулевой указатель.
  • strtok() использует нулевой указатель для последующих вызовов.

Если мы возьмем случай для snprintf(), имеет смысл разрешить передачу нулевого указателя, когда 'n' равно нулю, но это не относится к другим функциям (стандартной библиотеке), которые допускают аналогичный нулевой 'n'. Например: memcpy(), memmove(), strncpy(), memset(), memcmp().

Это не только указано во введении к стандартной библиотеке, но также еще раз во введении к этим функциям:

C99 §7.21.1 p2 / C11 §7.24.1 p2

Если аргумент, объявленный как size_t n, определяет длину массива для функции, n может иметь нулевое значение при вызове этой функции. Если иное явно не указано в описании конкретной функции в этом подпункте, аргументы указателя при таком вызове должны по-прежнему иметь допустимые значения, как описано в п. 7.1.4.


Это намеренно?

Я не знаю, является ли UB %p с нулевым указателем на самом деле намеренным, но поскольку в стандарте явно указано, что нулевые указатели считаются недопустимыми значениями в качестве аргументов стандартных библиотечных функций, а затем он идет и явно указывает случаи, когда нулевой указатель является допустимым аргументом (snprintf, free и т. д.), а затем он идет и еще раз повторяет требование, чтобы аргументы были действительными даже в нулевых случаях n (memcpy, memmove, memset), тогда я думаю, что это разумно предположить, что комитет по стандартам C не слишком озабочен тем, чтобы такие вещи оставались неопределенными.

person Dror K.    schedule 09.07.2017
comment
Комментарии не подлежат расширенному обсуждению; этот разговор был перешел в чат. - person Bhargav Rao; 09.07.2017
comment
Ваша вторая цитата, которую вы упомянули, совершенно неприменима, поскольку она касается передачи массивов в сочетании с size_t аргументами (что и относится к такому вызову). Наша попытка печати нулевого указателя не является таким вызовом, и недопустимо обобщать правила для этих конкретных вызовов на любую функцию, принимающую указатель. - person Jeroen Mostert; 09.07.2017
comment
@JeroenMostert Должен ли я предполагать, что, ничего не говоря о первой цитате, вы соглашаетесь с тем, что в ней говорится? Кстати, вы можете присоединиться к чату ^ - person Dror K.; 09.07.2017
comment
@DrorK .: нет, не надо. В аргументах языкового юриста всегда лучше сначала сосредоточиться на очевидном и отбросить то, что явно неверно / неуместно, прежде чем дойти до сути вопроса. Я вижу, как можно разумно спорить о 7.1.4, чего я сейчас делать не буду. - person Jeroen Mostert; 09.07.2017
comment
@JeroenMostert: В чем смысл этого аргумента? Приведенная цитата из 7.1.4 довольно ясна, не правда ли? О чем спорить по поводу , если явно не указано иное, когда не утверждается иначе? Что можно спорить с тем фактом, что (несвязанная) библиотека строковых функций имеет похожую формулировку, поэтому формулировка не кажется случайной? Я думаю, что этот ответ (хотя и не очень полезный на практике) настолько правильный, насколько это возможно. - person Damon; 10.07.2017
comment
@DrorK: Что касается вашего, я не знаю, преднамеренный ли это абзац: он почти наверняка преднамерен по соображениям совместимости. По общему признанию, я никогда не видел такого оборудования, но народ говорит, что в стране мифов и легенд существуют или существовали процессоры, где вы не можете хранить (хранить, а не разыменовать !) неверный указатель в адресном регистре, иначе вы вызовете аппаратное исключение. Таким образом, вы могли бы вызвать printf с недопустимым указателем в стеке, а затем ... бац. - person Damon; 10.07.2017
comment
@Damon: Ваше мифическое оборудование не является мифом, существует множество архитектур, в которых значения, не представляющие действительные адреса, могут не загружаться в регистры адресов. Однако передача нулевых указателей в качестве аргументов функции по-прежнему требуется для работы на этих платформах в качестве общего механизма. Простое добавление одного в стек ничего не взорвет. - person Jeroen Mostert; 10.07.2017
comment
@JeroenMostert Это интересно! Я знаю только x86, где регистры адресов могут содержать любое значение. У вас есть пример оборудования, которое будет жаловаться на неправильное значение в адресном регистре? - person anatolyg; 11.07.2017
comment
@anatolyg Может быть, уместнее задавать такие вопросы в чате - person Dror K.; 11.07.2017
comment
@anatolyg: В процессорах x86 адреса состоят из двух частей - сегмента и смещения. На 8086 загрузка сегментного регистра аналогична загрузке любого другого, но на всех более поздних машинах он извлекает дескриптор сегмента. Загрузка недопустимого дескриптора вызывает ловушку. Однако большая часть кода для процессоров 80386 и более поздних версий использует только один сегмент и, таким образом, никогда не загружает регистры сегментов вообще. - person supercat; 11.07.2017
comment
Проголосовано против - вопросы с самоответом SO - не место, чтобы попытаться выделить, в лучшем случае, незначительный дефект в формулировке стандарта. Вместо этого отправьте отчет о дефекте, если вы действительно считаете, что это проблема. - person M.M; 13.07.2017
comment
@ M.M Спасибо за комментарий, я полагаю, что считается «вежливым» указывать причину отрицательного голосования за ответ, но, поскольку ваши рассуждения не имеют ничего общего с ответом, мне кажется, что это неуместно. Нет ли на SO более подходящих мест для таких комментариев? Если нет, то еще раз - спасибо, что сообщили мне свое личное мнение о том, что должно или не должно быть на SO. Кроме того, спасибо за ваше предложение подать отчет о дефекте, но если что-нибудь, мой ответ указывает на то, что это не является дефектом. - person Dror K.; 14.07.2017
comment
Я думаю, все согласятся, что печать нулевого указателя с %p не должна быть неопределенным поведением. - person M.M; 14.07.2017
comment
@MM самостоятельно ответил на вопросы SO - не место, чтобы попытаться выделить, в лучшем случае, незначительный дефект, пожалуйста, докажите это. - person Stargateur; 14.07.2017
comment
Я согласен с этим ответом. Единственное, чего я не понимаю, - как можно принять ответ, противоположный их собственному? - person rustyx; 30.08.2018
comment
@ M.M: поведение %p определяется реализацией для ненулевых значений, и реализация может удовлетворять требованиям документации, даже если в ней указан способ преобразования, который приведет к непредсказуемому поведению для некоторых или всех таких значений. Любая реализация, которая соответствовала бы, если бы использование %p с нулевым указателем было UB, могла бы быть выполнена соответствующей, даже если бы такое действие было IDB, просто имея в своей документации способ преобразования, который привел бы к непредсказуемому поведению при задании нулевого указателя. Отнесение поведения к IDB ничего не изменит. - person supercat; 16.10.2018

Авторы стандарта C не приложили усилий, чтобы исчерпывающе перечислить все поведенческие требования, которым должна соответствовать реализация, чтобы соответствовать какой-либо конкретной цели. Вместо этого они ожидали, что люди, пишущие компиляторы, будут проявлять определенную долю здравого смысла независимо от того, требует этого Стандарт или нет.

Вопрос о том, вызывает ли что-то UB, редко бывает полезен сам по себе. Настоящие важные вопросы:

  1. Должен ли кто-то, кто пытается написать качественный компилятор, заставить его вести себя предсказуемым образом? Для описанного сценария ответ однозначно положительный.

  2. Должны ли программисты иметь право ожидать, что качественные компиляторы для чего-либо, напоминающего обычные платформы, будут вести себя предсказуемым образом? В описанном сценарии я бы сказал, что да.

  3. Могут ли некоторые тупые авторы компиляторов растянуть интерпретацию Стандарта, чтобы оправдать совершение чего-то странного? Надеюсь, что нет, но не исключаю.

  4. Должны ли дезинфицирующие компиляторы кричать о поведении? Это будет зависеть от уровня паранойи пользователей; дезинфицирующий компилятор, вероятно, не должен по умолчанию кричать о таком поведении, но, возможно, должен предоставить возможность конфигурации на случай, если программы могут быть перенесены на "умные" / глупые компиляторы, которые ведут себя странно.

Если разумная интерпретация Стандарта подразумевает, что поведение определено, но некоторые разработчики компиляторов растягивают интерпретацию, чтобы оправдать иное, имеет ли значение, что говорится в Стандарте?

person supercat    schedule 10.07.2017
comment
1. Программисты нередко обнаруживают, что предположения, сделанные современными / агрессивными оптимизаторами, расходятся с тем, что они считают разумным или качественным. 2. Когда дело доходит до двусмысленности в спецификации, разработчики нередко расходятся во мнениях относительно того, какие свободы они могут себе позволить. 3. Что касается членов комитета по стандартам C, то даже они не всегда соглашаются с тем, что такое «правильная» интерпретация, не говоря уже о том, какой она должна быть. С учетом вышесказанного, чьей разумной интерпретации мы должны следовать? - person Dror K.; 10.07.2017
comment
Отвечая на вопрос, вызывает ли этот конкретный фрагмент кода UB или нет, с диссертацией о том, что вы думаете о полезности UB или о том, как должны себя вести компиляторы, является плохой попыткой ответа, тем более что вы можете скопировать и вставить это в качестве ответа почти на любой вопрос о конкретном УБ. В ответ на вашу риторическую расцветку: да, действительно важно, что говорит стандарт, независимо от того, что делают некоторые разработчики компиляторов или что вы думаете о них, потому что стандарт - это то, с чего начинают и программисты, и разработчики компиляторов. - person Jeroen Mostert; 10.07.2017
comment
@JeroenMostert: ответ на вопрос, вызывает ли X неопределенное поведение, часто будет зависеть от того, что подразумевается под вопросом. Если программа считается имеющей неопределенное поведение, если стандарт не налагает никаких требований на поведение соответствующей реализации, то почти все программы вызывают UB. Авторы Стандарта четко позволяют реализациям вести себя произвольным образом, если программа слишком глубоко вкладывает вызовы функций, при условии, что реализация может правильно обрабатывать по крайней мере один (возможно, надуманный) исходный текст, который реализует ограничения на перевод в Stadard. - person supercat; 10.07.2017
comment
@supercat: очень интересно, но printf("%p", (void*) 0) поведение undefined или нет, согласно Стандарту? Вызов глубоко вложенных функций так же важен для этого, как и цена чая в Китае. И да, UB очень часто встречается в реальных программах - что из этого? - person Jeroen Mostert; 10.07.2017
comment
@JeroenMostert: Поскольку Стандарт позволяет тупой реализации рассматривать почти любую программу как имеющую UB, важным будет поведение не тупых реализаций. Если вы не заметили, я не просто написал копию / вставку о UB, но ответил на вопрос о %p для каждого возможного значения вопроса. - person supercat; 10.07.2017
comment
@supercat: ну, вот где мы категорически не согласны, поскольку мне кажется, что вы упускаете наиболее очевидное, общее значение вопроса, которое определенно рассматривается в двух других ответах. Но я подозреваю, что при таком расхождении во мнениях дальнейшее обсуждение никоим образом не улучшит ситуацию, поэтому я оставлю все как есть. - person Jeroen Mostert; 11.07.2017