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


194

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

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

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

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

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

Ответы:


240

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


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

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

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

§6.2.5 Типы

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

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

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

Таким образом, все указатели на структуры должны быть одинакового размера и должны иметь одинаковые требования выравнивания, даже если структуры, на которые указывают указатели, могут иметь разные требования выравнивания. Аналогично для профсоюзов. Указатели на символы и указатели на пустоту должны иметь одинаковые требования к размеру и выравниванию. Указатели на вариации 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 *без приведения.


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

Обратите внимание, что в обновлении POSIX (POSIX 2013) удален раздел 2.12.3, dlsym()вместо этого большинство функций перенесено в функцию. Однажды я запишу изменения ... но «один день» не «сегодня».
Джонатан Леффлер

Относится ли этот ответ также к указателям на функции? Могут ли они быть преобразованы в void *? Хм, я вижу твой комментарий здесь . Поскольку требуется преобразование только в один ватт (указатель на функцию void *), тогда оно работает?
chux - Восстановить Монику

@chux: Строго говоря, ответ «нет», но на практике ответ «да». Стандарт C не гарантирует, что указатели функций могут быть преобразованы в a void *и обратно без потери информации. Прагматически, очень мало машин, где размер указателя функции не совпадает с размером указателя объекта. Я не думаю, что стандарт предоставляет метод печати указателя функции на машинах, где преобразование проблематично.
Джонатан Леффлер

«и обратно без потери информации» не имеет отношения к печати. Это помогает?
chux - Восстановить Монику

50

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

int a = 42;

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

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


2
Простите, почему опускание актеров является "неопределенным поведением"? Имеет ли значение адрес этой переменной, если вам нужен только адрес, а не значение?
Вальдо

9
@valdo, потому что C говорит это (C99, 7.19.6.1p8) «p Аргумент должен быть указателем на void».
2012 года

12
@valdo: Это не обязательно тот случай, когда все указатели имеют одинаковый размер / представление.
Кафе

32

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

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

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

printf("x lives at %p.\n", (void*)&x);

2
Все указатели объектов могут быть преобразованы в void *(хотя для printf()вас технически требуется явное приведение, так как это функция с переменным числом аргументов). Указатели на функции не обязательно конвертируемы в void *.
Кафе

@caf: О, я не знал о вариативных аргументах - исправлено! Спасибо!
Керрек С.Б.

2
Стандарт C не требует, чтобы указатели функций были преобразованы в указатель функции void *и обратно без потерь; к счастью, POSIX явно требует этого (отмечая, что он не является частью стандарта C). Таким образом, на практике вы можете сойти с рук (преобразование void (*function)(void)в void *и обратно void (*function)(void)), но строго это не предусмотрено стандартом C.
Джонатан Леффлер

2
Джонатан и Р .: Это все очень интересно, но я уверен, что мы не пытаемся напечатать указатели функций здесь, так что, возможно, это не совсем подходящее место для обсуждения этого. Я бы предпочел увидеть некоторую поддержку здесь, чтобы настаивать на том, чтобы не использовать %u!
Керрек С.Б.

2
%uи %luошибаются на всех машинах , а не на некоторых машинах. Спецификация printfочень ясна: когда переданный тип не соответствует типу, требуемому спецификатором формата, поведение не определено. Соответствует ли размер типов (который может быть истинным или ложным, в зависимости от машины) не имеет значения; это типы, которые должны соответствовать, и они никогда не будут соответствовать.
R .. GitHub ОСТАНОВИТЬ ЛЬДА

9

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


рассмотреть ли #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } это неопределенное поведение для печати адреса переменной с использованием %uспецификатора формата? Адрес переменной в большинстве случаев положительный, так что я могу использовать %uвместо %p?
Деструктор

1
@Destructor: Нет, %uэто формат для unsigned intтипа и не может использоваться с аргументом указателя на printf.
R .. GitHub ОСТАНОВИТЬ ЛЬДА

-1

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

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

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

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


1
Добро пожаловать в ТАК. Пожалуйста, уделите некоторое время просмотру других ответов, в которых четко объясняется ряд деталей, которые вы пропускаете.
AntoineL
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.