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

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

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

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

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

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

    Самый простой ответ, если вы не возражаете против капризов и вариаций в формате между различными платформами, является стандартной записью %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 * для заданной ячейки памяти отличается от адреса « else else pointer » для того же места в памяти. Это был бы адресный адрес, а не байт-адрес, машина. В наши дни такие машины не распространены (вероятно, недоступны), но первая машина, над которой я работал после университета, была одной из таких (ICL Perq).

    Если вас не устраивает поведение %p , uintptr_t вместо этого используйте C99 и uintptr_t :

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

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


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

    Я немного смущен стандартными акциями и вариативными аргументами. Все ли указатели становятся стандартными для void *? В противном случае, если 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 * без трансляции.

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

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

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

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

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

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

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

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