C语言 打印指针或地址的格式说明符是否正确?

ahy6op9u  于 2023-05-06  发布在  其他
关注(0)|答案(6)|浏览(244)

我应该使用哪个格式说明符来打印变量的地址?我在下面的几个人之间感到困惑。
%u -无符号整数
%x -十六进制值
%p -空指针
哪种格式是打印地址的最佳格式?

z5btuh9x

z5btuh9x1#

假设您不介意不同平台之间格式的变化,最简单的答案是标准的%p表示法。
C99标准(ISO/IEC 9899:1999)在§7.19.6.1 ¶8中规定:
p参数应为指向void的指针。指针的值以实现定义的方式转换为打印字符序列。
(In C11 - ISO/IEC 9899:2011 -信息见§7.21.6.1 ¶8。)
在某些平台上,这将包括一个前导0x,而在其他平台上则不会,字母可以是小写或大写,C标准甚至没有定义它应该是十六进制输出,尽管我知道没有实现不是这样的。
是否应该使用(void *)强制转换显式地转换指针还有待讨论。它是显式的,这通常是好的(所以这是我所做的),标准说‘参数应该是指向void的指针’。在大多数机器上,省略显式强制转换就可以了。然而,在机器上,给定内存位置的char *地址的位表示与同一内存位置的“anything else pointer”地址不同。这将是一个字寻址,而不是字节寻址,机器。如今,这样的机器并不常见(可能是不可用的),但我大学毕业后工作的第一台机器就是这样的(ICL Perq)。
如果你对%p的实现定义的行为不满意,可以使用C99 <inttypes.h>uintptr_t

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

这允许您微调表示以适合自己。我选择了十六进制数字的大写形式,这样数字的高度就一致了,并且在0xA1B2CDEF的开头出现了特征性的下降,而不是像0xa1b2cdef那样沿着数字上下倾斜。你的选择,在很大的范围内。当GCC可以在编译时读取格式字符串时,它明确推荐使用(uintptr_t)强制转换。我认为这是正确的要求演员,虽然我相信有一些谁会忽视警告,并逃脱它的大部分时间。
Kerrek在评论中问道:
我对标准的促销和各种各样的争论有点困惑。是否所有指针都被标准提升为void*?否则,如果int*是两个字节,而void*是4个字节,那么从参数中读取4个字节显然是错误的,不是吗?
我误以为C标准规定所有对象指针必须大小相同,所以void *int *不能大小不同。然而,我认为C99标准的相关部分并不那么强调(尽管我不知道我建议的实现是真的实际上是假的):
§6.2.5类型
¶26指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求。39)同样,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求。所有指向结构类型的指针应具有彼此相同的表示和对齐要求。所有指向联合类型的指针应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。
39)相同的表示和对齐要求意味着作为函数的参数、函数的返回值和联合的成员的可互换性。
(C11在第6.2.5节,¶28和脚注48中也有同样的表述。)
因此,指向结构的所有指针必须彼此大小相同,并且必须共享相同的对齐要求,即使指针所指向的结构可能具有不同的对齐要求。工会也是如此。字符指针和空指针必须具有相同的大小和对齐要求。指向int变体(即unsigned intsigned int)的指针必须具有彼此相同的大小和对齐要求;对于其他类型也是如此。但是C标准并没有正式地说sizeof(int *) == sizeof(void *)。哦,好吧,所以让你检查你的假设是好的。
C标准明确地不要求函数指针与对象指针大小相同。这对于不破坏DOS类系统上的不同内存模型是必要的。在那里,你可以有16位的数据指针,但32位的函数指针,反之亦然。这就是为什么C标准不要求函数指针可以转换为对象指针,反之亦然。
幸运的是(对于以POSIX为目标的程序员来说),POSIX弥补了这一漏洞,并强制要求函数指针和数据指针大小相同:
§2.12.3指针类型

所有函数指针类型都应具有与指向void的类型指针相同的表示形式。将函数指针转换为void *不应改变表示。通过这种转换得到的void *值可以使用显式强制转换转换回原始函数指针类型,而不会丢失信息。
注意:ISO C标准不要求这样做,但它是POSIX一致性所必需的。
因此,在传递一个指向可变参数函数(如printf())的指针时,为了最大限度地提高代码的可靠性,强烈建议显式强制转换为void *。在POSIX系统上,将函数指针转换为空指针以进行打印是安全的。在其他系统上,这样做不一定安全,传递除void *以外的指针而不进行强制转换也不一定安全。

yvt65v4c

yvt65v4c2#

p是打印指针的转换说明符。用这个。

int a = 42;

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

请记住,省略强制转换是未定义的行为,并且使用p转换说明符打印是以实现定义的方式完成的。

s8vozzvw

s8vozzvw3#

使用%p表示“指针”,不要使用其他任何东西 *。标准不能保证你可以像对待任何特定类型的整数一样对待指针,所以你实际上会得到整型格式的未定义行为。(例如,%u需要unsigned int,但是如果void*的大小或对齐要求与unsigned int不同,该怎么办?)
”(见乔纳森的精彩回答。]除了%p,你还可以 * 使用C99中添加的<inttypes.h>中的指针特定宏。
所有的对象指针在C中都可以隐式转换为void*,但是为了将指针作为可变参数传递,你必须显式地转换它(因为任意对象指针只 * 可转换 *,但不 * 等同于void指针):

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

7fyelxc54#

作为其他(非常好的)答案的替代方案,您可以转换为uintptr_tintptr_t(从stdint.h/inttypes.h)并使用相应的整数转换说明符。这将允许在如何格式化指针方面有更大的灵活性,但严格地说,实现不需要提供这些typedef。

syqv5f0l

syqv5f0l5#

您可以使用%x%X%p;都是正确的。

  • 如果使用%x,则地址以小写形式给出,例如:a3bfbc4
  • 如果使用%X,则地址以大写形式给出,例如:A3BFBC4

这两个都是正确的。
如果使用%x%X,则会考虑地址的六个位置,如果使用%p,则会考虑地址的八个位置。例如:

pbpqsu0x

pbpqsu0x6#

标准是%p
如果%x在64位系统上只打印整个地址的一部分,请使用%lx%llx

相关问题