正确的格式说明符打印指针或地址?

San*_*San 167 c format pointers memory-address

我应该使用哪种格式说明符来打印变量的地址?下面很多我很困惑.

%u - 无符号整数

%x - 十六进制值

%p - 无效指针

哪个是打印地址的最佳格式?

Jon*_*ler 213

最简单的答案,假设您不介意不同平台之间的变幻莫测和格式变化,是标准%p符号.

C99标准(ISO/IEC 9899:1999)在§7.19.6.18中说:

p参数应该是指针void.指针的值以实现定义的方式转换为打印字符序列.

(在C11 - ISO/IEC 9899:2011中 - 信息在§7.21.6.18中.)

在某些平台上,这将包括一个领先,0x而在其他平台上它将不会,并且字母可以是小写或大写,并且C标准甚至没有定义它应该是十六进制输出,虽然我知道没有实施的地方没有.

对于是否应该使用强制转换显式转换指针,我们有一定的争论(void *).它是明确的,通常是好的(所以我这样做),标准说'参数应该是指向void'.在大多数机器上,你可以省略一个明确的演员表.然而,在char *给定存储器位置的地址的位表示与相同存储器位置的" 任何其他指针 "地址不同的机器上,这将是重要的.这将是一个字寻址,而不是字节寻址的机器.这些机器现在并不常见(可能不可用),但我在大学毕业后使用的第一台机器就是其中之一(ICL Perq).

如果你不满意的实现定义%p,然后用C99 <inttypes.h>uintptr_t替代:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);
Run Code Online (Sandbox Code Playgroud)

这允许您微调表示以适合自己.我选择以大写字母表示十六进制数字,这样数字的高度统一相同,0xA1B2CDEF因此在出现时会出现特征性的下降,而不是0xa1b2cdef沿着数字向上和向下倾斜.您的选择虽然在很宽的范围内.该(uintptr_t)投是通过明确推荐GCC时,它可以读取在编译时的格式字符串.我认为请求演员是正确的,但我确信有些人会忽略警告并且大部分时间都会逃避警告.


Kerrek在评论中提问:

我对标准促销和可变参数有点困惑.所有指针都被标准提升为void*吗?否则,如果int*是两个字节,并且void*是4个字节,那么从参数读取四个字节显然是错误的,非?

我是C标准说,所有的对象指针必须是相同的大小,所以这样的误解,void *int *不能是不同的尺寸.但是,我认为C99标准的相关部分并不是那么强调(虽然我不知道我所建议的是真实的实现是错误的):

§6.2.5类型

26指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求.39)类似地,指向兼容类型的合格或非限定版本的指针应具有相同的表示和对齐要求.所有指向结构类型的指针都应具有相同的表示和对齐要求.所有指向union类型的指针都应具有相同的表示和对齐要求.指向其他类型的指针不需要具有相同的表示或对齐要求.

39)相同的表示和对齐要求意味着可互换性作为函数的参数,函数的返回值和联合的成员.

(C11在§6.2.5,28和脚注48一节中完全相同)

因此,所有指向结构的指针必须具有相同的大小,并且必须共享相同的对齐要求,即使指针指向的结构可能具有不同的对齐要求.工会也是如此.字符指针和void指针必须具有相同的大小和对齐要求.对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一致性所必需的.

因此,void *当将指针传递给可变参数函数时,似乎强烈建议将显式强制转换为代码中的最大可靠性printf().在POSIX系统上,将函数指针强制转换为void指针进行打印是安全的.在其他系统上,这样做并不一定安全,除了void *没有强制转换之外,传递指针也不一定安全.

  • 我对标准促销和可变参数有点困惑.所有指针都被标准升级为`void*`吗?否则,如果`int*`是两个字节,而`void*`是4个字节,那么从参数读取四个字节显然是错误的,非? (3认同)

oua*_*uah 42

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

int a = 42;

printf("%p\n", (void *) &a);
Run Code Online (Sandbox Code Playgroud)

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

  • @valdo:并不一定所有指针都具有相同的大小/表示. (12认同)
  • @valdo因为C说它(C99,7.19.6.1p8)"p参数应该是指向void的指针." (9认同)
  • 对不起,为什么省略演员表是“未定义的行为”?如果您只需要地址而不是值,这是否与它是哪个变量的地址有关? (2认同)

Ker*_* SB 28

使用%p,"指针",不要使用任何其他*.标准不保证您可以像任何特定类型的整数一样处理指针,因此您实际上会使用整数格式获得未定义的行为.(例如,%u期望一个unsigned int,但如果void*有不同的大小或对齐要求,那该unsigned int怎么办?)

*)[请参阅Jonathan的精彩答案!]或者%p,您可以使用<inttypes.h>C99中添加的特定于指针的宏.

所有对象指针都可以隐式转换为void*C语言,但是为了将指针作为可变参数传递,您必须显式地转换它(因为任意对象指针只能转换,但 void指针不同):

printf("x lives at %p.\n", (void*)&x);
Run Code Online (Sandbox Code Playgroud)

  • 所有*object*指针都可以转换为`void*`(虽然对于`printf()`你在技术上需要显式转换,因为它是一个可变函数).函数指针不一定可以转换为`void*`. (2认同)
  • 标准C不要求函数指针可以转换为`void*`并返回函数指针而不会丢失; 幸运的是,POSIX明确要求(注意它不是标准C的一部分).所以,在实践中,你可以逃脱它(将`void(*function)(void)`转换为`void*`并返回`void(*function)(void)`),但严格来说,它不是强制要求的C标准. (2认同)
  • Jonathan和R.:这一切都非常有趣,但我很确定我们不打算在这里打印函数指针,所以也许这不是讨论这个的正确位置.我宁愿在这里看到一些支持,因为我坚持不使用'%u`! (2认同)
  • **%u`和`%lu`在**所有机器**上都是错误的,而不是某些机器.`printf`的规范非常明确,当传递的类型与格式说明符所需的类型不匹配时,行为是未定义的.类型的大小是否匹配(可能是真或假,取决于机器)是无关紧要的; 它是必须匹配的类型,它们永远不会. (2认同)

R..*_*R.. 8

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


小智 6

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

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

这两个都是正确的。

如果您使用%x%X它正在考虑地址的六个位置,并且如果您使用%p它则考虑地址的八个位置。例如:

  • 欢迎来到SO。请花一些时间查看其他答案,它们清楚地解释了您忽略的许多细节。 (2认同)