Самый простой ответ, если вы не возражаете против капризов и различий в формате между различными платформами, - это стандартная запись %p
.
Стандарт C99 (ISO / IEC 9899: 1999) гласит в §7.19.6.1 ¶8:
p
Аргумент должен быть указателем на void
. Значение указателя
преобразуется в последовательность печатных символов в определенной реализацией
образом.
(в C11 - ISO / IEC 9899: 2011 - информация в §7.21.6.1 .18.)
На некоторых платформах, которые будут содержать начальный 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, когда он может прочитать строку формата во время компиляции. Я думаю, что правильно запрашивать актерский состав, хотя я уверен, что есть некоторые, которые игнорируют предупреждение и избегают его большую часть времени.
Керрек спрашивает в комментариях:
Я немного запутался в стандартных акциях и вариативных аргументах. Все ли указатели стандартно повышаются до void *? В противном случае, если бы int*
были, скажем, двумя байтами, а void*
были 4 байтами, то было бы ошибкой считывать четыре байта из аргумента, не так ли?
У меня была иллюзия, что стандарт C говорит, что все указатели объектов должны быть одинакового размера, поэтому void *
и int *
не могут быть разных размеров. Однако то, что я считаю соответствующим разделом стандарта C99, не столь решительно (хотя я не знаю реализации, где то, что я предложил, является истинным, на самом деле является ложным):
§6.2.5 Типы
¶26 Указатель на void должен иметь те же требования к представлению и выравниванию, что и указатель на тип символа. 39) Аналогично, указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь то же представление и Требования к выравниванию. Все указатели на типы конструкций должны иметь те же требования к представлению и выравниванию, что и другие. Все указатели на типы объединения должны иметь те же требования к представлению и выравниванию, что и другие. Указатели на другие типы не обязательно должны иметь одинаковые требования к представлению или выравниванию.
39) Те же требования к представлению и выравниванию подразумевают взаимозаменяемость в качестве аргументов функций, возвращаемых значений из функций и членов объединений.
(C11 говорит то же самое в разделе §6.5.5, §28 и сноске 48.)
Итак, всеl Указатели на структуры должны иметь одинаковый размер и должны иметь одинаковые требования к выравниванию, даже если структуры, на которые указывают указатели, могут иметь разные требования к выравниванию. Аналогично для профсоюзов. Указатели на символы и указатели на пустоту должны иметь одинаковые требования к размеру и выравниванию. Указатели на варианты 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 *
без приведения.