Правильный ли спецификатор формата для печати указателя или адреса? - PullRequest
157 голосов
/ 29 января 2012

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

% u - целое число без знака

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

% p - указатель void

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

Ответы [ 4 ]

195 голосов
/ 29 января 2012

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

39 голосов
/ 29 января 2012

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

int a = 42;

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

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

26 голосов
/ 29 января 2012

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

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

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

printf("x lives at %p.\n", (void*)&x);
8 голосов
/ 29 января 2012

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

...