Самый простой ответ, если вы не возражаете против капризов и вариаций формата между разными платформами, - это стандартная нотация %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