Как правильно использовать printf для отображения указателей, дополненных нулями?
Вопрос
В C я хотел бы использовать printf для отображения указателей, и чтобы они правильно выстраивались в линию, я хотел бы дополнить их нулями.
Я предполагал, что правильный способ сделать это:
printf("%016p", ptr);
Это работает, но этот gcc жалуется на следующее сообщение:
warning: '0' flag used with ‘%p’ gnu_printf format
Я немного погуглил, и следующая ветка посвящена той же теме, но на самом деле не дает решения.
http://gcc.gnu.org/ml/gcc-bugs/2003-05/msg00484.html
Читая его, кажется, что причина, по которой gcc жалуется, заключается в том, что предложенный мной синтаксис не определен в C99.Но я не могу найти другого способа сделать то же самое стандартным способом.
Итак, двойной вопрос:
- Правильно ли я понимаю, что такое поведение не определено стандартом C99?
- Если да, то существует ли стандартный одобренный портативный способ сделать это?
Решение
#include <inttypes.h>
#include <stdint.h>
printf("%016" PRIxPTR "\n", (uintptr_t)ptr);
но он не будет печатать указатель способом, определенным реализацией (говорит DEAD:BEEF
для сегментированного режима 8086).
Другие советы
Использовать:
#include <inttypes.h>
printf("0x%016" PRIXPTR "\n", (uintptr_t) pointer);
Либо используйте другой вариант макроса из этого заголовка.
Также обратите внимание, что некоторые реализации printf()
напечатайте '0x
' перед указателем;другие нет (и оба верны в соответствии со стандартом C).
Единственный портативный способ печати значений указателя — использовать "%p"
спецификатор, который производит выходные данные, определенные реализацией.(Преобразование в uintptr_t
и использование PRIxPTR
скорее всего сработает, но (а) uintptr_t
был представлен в C99, поэтому некоторые компиляторы могут его не поддерживать, и (б) его существование не гарантируется даже в C99 (возможно, не существует целочисленного типа, достаточно большого для хранения всех возможных значений указателя.
Так что используйте "%p"
с sprintf()
(или snprintf()
, если оно у вас есть) для печати в строку, а затем напечатайте заполнение строки начальными пробелами.
Одна из проблем заключается в том, что не существует действительно хорошего способа определить максимальную длину строки, создаваемой "%p"
.
Это, конечно, неудобно (хотя, конечно, можно обернуть это в функцию).Если вам не нужна 100% переносимость, я никогда не использовал систему, на которую приводился указатель. unsigned long
и распечатать результат с помощью "0x%16lx"
не сработает.[ОБНОВЛЯТЬ:Да, у меня есть.В 64-битной Windows есть 64-битные указатели и 32-битные указатели. unsigned long
.] (Или вы можете использовать "0x%*lx"
и вычислить ширину, возможно, что-то вроде sizeof (void*) * 2
.Если вы действительно параноик, вы можете объяснить системы, в которых CHAR_BIT > 8
, поэтому вы получите более 2 шестнадцатеричных цифр на байт.)
Этот ответ аналогичен тому, который был дан ранее в https://stackoverflow.com/a/1255185/1905491 но также принимает во внимание возможную ширину (как указано в https://stackoverflow.com/a/6904396/1905491 который я не узнал, пока мой ответ не был представлен под ним;).Следующий фрагмент будет печатать указатели в виде 8 шестнадцатеричных символов с нулевой передачей на машинах sane*, где они могут быть представлены 32 битами, 16 в системах 64b и 4 в системах 16b.
#include <inttypes.h>
#define PRIxPTR_WIDTH ((int)(sizeof(uintptr_t)*2))
printf("0x%0*" PRIxPTR, PRIxPTR_WIDTH, (uintptr_t)pointer);
Обратите внимание на использование символа звездочки для получения ширины по следующему аргументу, который есть в C99 (вероятно, раньше?), но который довольно редко встречается «в дикой природе».Это намного лучше, чем использовать p
преобразование, поскольку последнее определяется реализацией.
* Стандарт позволяет uintptr_t
быть больше минимума, но я предполагаю, что не существует реализации, которая не использовала бы минимум.
Эту проблему легко решить, если привести указатель к длинному типу.Проблема в том, что это не будет работать на всех платформах, поскольку предполагается, что указатель может помещаться внутри длинного элемента и что указатель может отображаться в 16 символах (64 бита).Однако это верно для большинства платформ.
так что это будет:
printf("%016lx", (unsigned long) ptr);
Как уже следует из вашей ссылки, поведение не определено.Я не думаю, что существует портативный способ сделать это, поскольку сам %p зависит от платформы, на которой вы работаете.Конечно, вы можете просто привести указатель к int и отобразить его в шестнадцатеричном виде:
printf("0x%016lx", (unsigned long)ptr);
Возможно, это будет интересно (на 32-битной машине Windows с использованием mingw):
rjeq@RJEQXPD /u
$ cat test.c
#include <stdio.h>
int main()
{
char c;
printf("p: %016p\n", &c);
printf("x: %016llx\n", (unsigned long long) (unsigned long) &c);
return 0;
}
rjeq@RJEQXPD /u
$ gcc -Wall -o test test.c
test.c: In function `main':
test.c:7: warning: `0' flag used with `%p' printf format
rjeq@RJEQXPD /u
$ ./test.exe
p: 0022FF77
x: 000000000022ff77
Как видите, версия %p дополняет нулями до размера указателя, а затем пробелами до указанного размера, тогда как при использовании %x и приведения (описатели приведения и формата крайне непереносимы) используются только нули.
Обратите внимание: если указатель печатается с префиксом «0x», для компенсации вам потребуется заменить его на «%018p».
Обычно я использую %x для отображения указателей.Я полагаю, что это не переносится на 64-битные системы, но отлично работает на 32-битных.
Мне будет интересно посмотреть, какие ответы есть на этот вопрос. портативные решения, поскольку представление указателя не совсем переносимо.