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

Какой спецификатор формата я должен использовать для печати адреса переменной? Я запутался в приведенном ниже лоте.

% u - беззнаковое целое

% x - шестнадцатеричное значение

% p - пустой указатель

Какой формат будет оптимальным для печати адреса?


person San    schedule 29.01.2012    source источник


Ответы (5)


Самый простой ответ, если вы не возражаете против капризов и вариаций формата между разными платформами, - это стандартная нотация %p.

Стандарт C99 (ISO / IEC 9899: 1999) говорит в §7.19.6.1 ¶8:

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

(В C11 - ISO / IEC 9899: 2011 - информация в §7.21.6.1 ¶8.)

На некоторых платформах он будет включать начальный 0x, а на других - нет, и буквы могут быть в нижнем или верхнем регистре, а стандарт C даже не определяет, что это должен быть шестнадцатеричный вывод, хотя я знаю нет реализации там, где ее нет.

В некоторой степени открыт спор о том, следует ли вам явно преобразовывать указатели с приведением (void *). Он явный, что обычно хорошо (так что я так и делаю), и в стандарте сказано: «аргумент должен быть указателем на void». На большинстве машин вы можете избежать явного приведения типов. Однако это будет иметь значение на машине, где битовое представление адреса char * для данной ячейки памяти отличается от адреса «любой другой указатель» для той же ячейки памяти. Это будет машина с адресацией по словам, а не с байтовой адресацией. Такие машины не распространены (вероятно, недоступны) в наши дни, но первая машина, над которой я работал после университета, была одной из них (ICL Perq).

Если вас не устраивает поведение %p, определяемое реализацией, используйте вместо этого C99 <inttypes.h> и uintptr_t:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

Это позволяет вам настроить представление по своему усмотрению. Я решил использовать шестнадцатеричные цифры в верхнем регистре, чтобы числа имели одинаковую высоту и, таким образом, появлялся характерный провал в начале 0xA1B2CDEF, в отличие от 0xa1b2cdef, который также спускается вверх и вниз по номеру. Ваш выбор, правда, в очень широких пределах. Приведение (uintptr_t) однозначно рекомендуется GCC, когда он может читать строку формата во время компиляции. Я считаю правильным запросить кастинг, хотя уверен, что есть такие, кто проигнорирует предупреждение и большую часть времени уйдет с рук.


Керрек спрашивает в комментариях:

Я немного смущен стандартными предложениями и вариативными аргументами. Все ли указатели по стандарту становятся недействительными *? В противном случае, если бы int* было, скажем, двумя байтами, а void* было 4 байтами, тогда было бы явно ошибкой читать четыре байта из аргумента, не?

У меня была иллюзия, что стандарт C говорит, что все указатели объектов должны быть одного размера, поэтому void * и int * не могут быть разных размеров. Однако то, что я считаю соответствующим разделом стандарта C99, не столь категорично (хотя я не знаю реализации, в которой то, что я предложил, верно, на самом деле ложно):

§6.2.5 Типы

¶26 Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на символьный тип. 39) Точно так же указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь те же требования к представлению и выравниванию. . Все указатели на структурные типы должны иметь одинаковые требования к представлению и выравниванию. Все указатели на типы объединения должны иметь одинаковые требования к представлению и выравниванию. Указатели на другие типы не обязательно должны иметь такие же требования к представлению или выравниванию.

39) Те же требования к представлению и выравниванию подразумевают взаимозаменяемость аргументов функций, возвращаемых значений из функций и членов объединений.

(C11 говорит то же самое в разделе §6.2.5, ¶28 и сноске 48.)

Таким образом, все указатели на структуры должны иметь одинаковый размер и иметь одинаковые требования к выравниванию, даже если структуры, на которые указывают указатели, могут иметь разные требования к выравниванию. Аналогично для профсоюзов. Указатели символов и указатели void должны иметь одинаковые требования к размеру и выравниванию. Указатели на варианты int (означающие unsigned int и signed int) должны иметь одинаковые требования к размеру и выравниванию; аналогично для других типов. Но стандарт C официально не говорит, что sizeof(int *) == sizeof(void *). Ну что ж, ТАК хорош для того, чтобы заставить вас проверить свои предположения.

Стандарт C однозначно не требует, чтобы указатели на функции были того же размера, что и указатели на объекты. Это было необходимо, чтобы не сломать различные модели памяти в DOS-подобных системах. Там у вас могут быть 16-битные указатели данных, но 32-битные указатели на функции, или наоборот. Вот почему стандарт C не требует, чтобы указатели функций могли быть преобразованы в указатели объектов и наоборот.

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

§2.12.3 Типы указателей

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

Примечание. Стандарт ISO C не требует этого, но требуется для соответствия POSIX.

Таким образом, кажется, что явное приведение к void * настоятельно рекомендуется для максимальной надежности кода при передаче указателя на вариативную функцию, такую ​​как printf(). В системах POSIX можно безопасно преобразовать указатель функции на указатель void для печати. В других системах это не обязательно безопасно, и также небезопасно передавать указатели, отличные от void *, без приведения.

person Jonathan Leffler    schedule 29.01.2012
comment
Я немного смущен стандартными предложениями и вариативными аргументами. Все ли указатели повышаются по стандарту до void*? В противном случае, если бы int* было, скажем, двумя байтами, а void* было 4 байтами, тогда было бы явно ошибкой читать четыре байта из аргумента, не? - person Kerrek SB; 29.01.2012
comment
Обратите внимание, что обновление POSIX (POSIX 2013) удалило раздел 2.12.3, перенеся большинство требований в _ 1_ вместо этого. Однажды я запишу изменение ... но «однажды» - это не «сегодня». - person Jonathan Leffler; 04.06.2014
comment
Относится ли этот ответ также к указателям на функции? Можно ли их преобразовать в void *? Хм, я вижу ваш комментарий здесь. Поскольку требуется преобразование только в одну ватт (указатель функции на void *), тогда он работает? - person chux - Reinstate Monica; 03.10.2015
comment
@chux: Строго говоря, ответ - «нет», но на практике ответ - «да». Стандарт C не гарантирует, что указатели функций могут быть преобразованы в void * и обратно без потери информации. Прагматически очень мало машин, на которых размер указателя функции не совпадает с размером указателя объекта. Я не думаю, что стандарт предоставляет метод печати указателя функции на машинах, где преобразование проблематично. - person Jonathan Leffler; 03.10.2015
comment
и обратно без потери информации не имеет отношения к печати. Это поможет? - person chux - Reinstate Monica; 03.10.2015
comment
@chux: Нет; если преобразование не может быть выполнено по очереди, напечатанное значение может оказаться бесполезным. Это будет a представление указателя функции, но оно может быть непригодным для использования и не точным. Например, если вы были на машине, на которой указатель на функцию был 128 бит, а указатель void был 64 бит, вы могли бы использовать printf("%p\n", (void *)funcptr);, но на самом деле не ясно, помогает ли это, более чем printf("%d\n", (int)longvalue); надежно полезно, если sizeo(int) != sizeof(long) и значение в longvalue больше, чем умещается в int. - person Jonathan Leffler; 03.10.2015
comment
@JonathanLeffler: подумайте, #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } это неопределенное поведение для печати адреса переменной с использованием спецификатора формата %u? Адрес переменной в большинстве случаев положительный, поэтому могу ли я использовать %u вместо %p? - person Destructor; 19.12.2016
comment
В 64-битной системе вы будете печатать 32-битное значение с %u, но ваш указатель был 64-битным. Да, это не определено. Если вы хотите напечатать указатель в целочисленных форматах, используйте некоторые варианты PRIXPTR, как показано в моем ответе. - person Jonathan Leffler; 19.12.2016
comment
Это имело бы значение на машине с другим представлением для char *. Верно, особенно на ПК, где есть дальние указатели. - person ; 08.01.2017
comment
Одной из реализаций, где это было заведомо ложным, была первая реализация C. - person Antti Haapala; 19.03.2017
comment
@AnttiHaapala: да, %p получил широкое распространение со стандартом C89 / C90. Есть множество других функций, которых не было в исходной стандартной библиотеке ввода-вывода 1978 года или около того (возможно, на пару лет раньше), но эти проблемы редко бывают актуальными для кого-либо. Когда это проблема, программист обычно знает, какая документация применима. В таких средах нет long long, uintptr_t или… ни того, ни другого. В предыстории C. - person Jonathan Leffler; 19.03.2017

p - спецификатор преобразования в указатели печати. Использовать это.

int a = 42;

printf("%p\n", (void *) &a);

Помните, что пропуск преобразования является неопределенным поведением и что печать со спецификатором преобразования p выполняется способом, определяемым реализацией.

person ouah    schedule 29.01.2012
comment
Простите, почему исключение приведений является неопределенным поведением? Имеет ли значение адрес переменной, если все, что вам нужно, это адрес, а не значение? - person valdo; 29.01.2012
comment
@valdo, потому что C говорит это (C99, 7.19.6.1p8) p Аргумент должен быть указателем на void. - person ouah; 29.01.2012
comment
@valdo: не обязательно, чтобы все указатели имели одинаковый размер / представление. - person caf; 29.01.2012

Используйте %p для «указателя» и больше ничего не используйте *. Стандарт не гарантирует, что вам разрешено обрабатывать указатель как любой конкретный тип целого числа, поэтому вы фактически получите неопределенное поведение с интегральными форматами. (Например, %u ожидает unsigned int, но что, если void* имеет другие требования к размеру или выравниванию, чем unsigned int?)

*) [См. Прекрасный ответ Джонатана!] В качестве альтернативы %p вы можете использовать специфичные для указателя макросы из <inttypes.h>, добавленные в C99.

Все указатели на объекты неявно преобразуются в void* в C, но для того, чтобы передать указатель в качестве вариативного аргумента, вы должны явно привести его (поскольку произвольные указатели на объекты могут быть только конвертируемыми, но не идентично недействительным указателям):

printf("x lives at %p.\n", (void*)&x);
person Kerrek SB    schedule 29.01.2012
comment
Все указатели object могут быть преобразованы в void * (хотя для printf() вам технически требуется явное приведение, поскольку это вариативная функция). Указатели функций не обязательно конвертируются в void *. - person caf; 29.01.2012
comment
@caf: О, я не знал о вариативных аргументах - исправлено! Спасибо! - person Kerrek SB; 29.01.2012
comment
Стандарт C не требует, чтобы указатели функций были преобразованы в void * и обратно в указатель функции без потерь; к счастью, однако, POSIX явно требует этого (с учетом того, что он не является частью стандарта C). Таким образом, на практике это может сойти с рук (преобразование void (*function)(void) в void * и обратно в void (*function)(void)), но строго это не предусмотрено стандартом C. - person Jonathan Leffler; 29.01.2012
comment
Джонатан и Р .: Все это очень интересно, но я почти уверен, что мы не пытаемся печатать здесь указатели на функции, так что, возможно, это не совсем подходящее место для обсуждения этого. Я бы предпочел увидеть здесь поддержку моего настойчивого требования не использовать %u! - person Kerrek SB; 29.01.2012
comment
Хорошо, удалил мой довольно неуместный и неправильный комментарий. Не стесняйтесь делать то же самое со своим, если хотите. И +1 за то, что сказал не использовать ужасно неправильные спецификаторы формата, такие как %u. Хотя, строго говоря, я бы эту фразу убрал. ИМО, это относит проблему к области языковых юристов, когда на самом деле использование неправильного спецификатора является серьезной реальной ошибкой. - person R.. GitHub STOP HELPING ICE; 29.01.2012
comment
Я согласен с тем, что и %u, и %lu неправильны на некоторых машинах, и что %p намного лучше, чем любой из них. Однако есть два целочисленных типа, которые вы можете надежно использовать - intptr_t и uintptr_t (хотя почему вы хотите использовать подписанную версию, я не уверен!). Смотрите мой ответ. Так что да - то, что вы говорите, в основном хорошее. (Я удалил то, что считаю спорной частью обмена с @R ..). - person Jonathan Leffler; 29.01.2012
comment
@JonathanLeffler: Ах, да, почтенные <inttypes.h> макросы ... действительно, достойны отдельной главы! - person Kerrek SB; 29.01.2012
comment
%u и %lu неверны на всех машинах, а не на некоторых. Спецификация printf очень ясно показывает, что когда переданный тип не соответствует типу, требуемому спецификатором формата, поведение не определено. Не имеет значения, соответствует ли размер типов (что может быть истинным или ложным, в зависимости от машины); это типы, которые должны совпадать, и они никогда не будут совпадать. - person R.. GitHub STOP HELPING ICE; 29.01.2012
comment
@KerrekSB: подумайте, #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } это неопределенное поведение для печати адреса переменной с использованием спецификатора формата% u? Адрес переменной в большинстве случаев положительный, поэтому могу ли я использовать %u вместо %p? - person Destructor; 19.12.2016
comment
@Destructor: %u ожидает unsigned int, а вы передаете int *, так что это UB. - person Kerrek SB; 19.12.2016

В качестве альтернативы другим (очень хорошим) ответам вы можете привести к uintptr_t или intptr_t (из _3 _ / _ 4_) и использовать соответствующие спецификаторы целочисленного преобразования. Это обеспечит большую гибкость в том, как форматируется указатель, но, строго говоря, реализация для предоставления этих определений типов не требуется.

person R.. GitHub STOP HELPING ICE    schedule 29.01.2012
comment
подумайте, #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } это неопределенное поведение для печати адреса переменной с использованием %u спецификатора формата? Адрес переменной в большинстве случаев положительный, поэтому могу ли я использовать %u вместо %p? - person Destructor; 19.12.2016
comment
@Destructor: Нет, %u является форматом для типа unsigned int и не может использоваться с аргументом указателя на printf. - person R.. GitHub STOP HELPING ICE; 19.12.2016

Вы можете использовать %x, %X или %p; все они верны.

  • Если вы используете %x, адрес указывается в нижнем регистре, например: a3bfbc4
  • Если вы используете %X, адрес указывается в верхнем регистре, например: A3BFBC4

Оба они верны.

Если вы используете %x или %X, он учитывает шесть позиций для адреса, а если вы используете %p, он учитывает восемь позиций для адреса. Например:

person Pasha    schedule 18.02.2020
comment
Добро пожаловать в SO. Найдите время, чтобы просмотреть другие ответы, они ясно объясняют ряд деталей, которые вы упускаете из виду. - person AntoineL; 18.02.2020