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


192

我应该使用哪种格式说明符来打印变量的地址?我对以下很多东西感到困惑。

%u-无符号整数

%x-十六进制值

%p-无效指针

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

Answers:


239

假设您不介意不同平台之间格式的变化和变化,最简单的答案就是标准%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时,它可以读取在编译时的格式字符串。我认为请求强制转换是正确的,尽管我敢肯定有些人会忽略警告并在大多数情况下逃避它。


Kerrek在评论中要求:

我对标准的促销活动和各种各样的争论感到困惑。所有指针是否都标准提升为void *?否则,如果int*说是两个字节,又void*是4个字节,那么从参数中读取四个字节显然是错误的,不是吗?

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

§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要求,它是必需的。

因此,void *在将变量传递给诸如的可变参数函数的指针时,似乎强烈建议显式强制转换为最大的代码可靠性printf()。在POSIX系统上,将函数指针强制转换为空指针以进行打印是安全的。在其他系统上,这样做不一定是安全的,传递指针而不是强制转换也不一定安全void *


3
我对标准的促销活动和各种各样的争论感到困惑。是否所有指针都被标准提升为void*?否则,如果int*说是两个字节,又void*是4个字节,那么从参数中读取四个字节显然是错误的,不是吗?
Kerrek SB 2012年

请注意,对POSIX的更新(POSIX 2013)删除了第2.12.3节,将大部分要求移至了该dlsym()功能。有一天我会写下更改...但是“一天”不是“今天”。
Jonathan Leffler 2014年

这个答案也适用于函数指针吗?可以将它们转换为void *吗?嗯,我在这里看到你的评论。由于只需要一瓦转换(指向的函数指针void *),那么它工作吗?
chux-恢复莫妮卡2015年

@chux:严格来说,答案是“否”,但实际上答案是“是”。C标准不保证可以将函数指针转换为a void *和反向转换而不会丢失信息。实用上,很少有机器的函数指针的大小与对象指针的大小不同。我认为该标准没有提供在转换有问题的机器上打印功能指针的方法。
乔纳森·莱夫勒

“返回而不会丢失信息”与打印无关。有帮助吗?
chux-恢复莫妮卡2015年

50

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

int a = 42;

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

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


2
请原谅,为什么省略演员表是“未定义的行为”?这关系到它是哪个变量的地址,如果您需要的只是地址而不是值呢?
valdo 2012年

9
@valdo,因为C会这样说(C99,7.19.6.1p8)“ p参数应为指向void的指针。”
哇,2012年

12
@valdo:不一定所有的指针都具有相同的大小/表示形式。
CAF

32

%p用作“指针”,请勿使用其他任何*。该标准不能保证允许您将指针像任何特定类型的整数一样对待,因此使用整数格式实际上会获得未定义的行为。(例如,%u期望使用unsigned int,但如果void*尺寸或对齐要求与unsigned int?不同,该怎么办?)

*)[请参阅Jonathan的最佳答案!]除了%p,您还可以使用<inttypes.h>C99中添加的的指针专用宏。

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

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

2
所有对象指针都可以转换为void *(尽管从printf()技术上讲,您需要显式强制转换,因为它是可变参数)。函数指针不一定可以转换为void *
caf 2012年

@caf:哦,我不知道可变参数-固定!谢谢!
Kerrek SB 2012年

2
标准C并不要求函数指针可以在void *不损失的情况下转换为函数指针,也可以转换回该函数指针。不过,幸运的是,POSIX确实明确要求(请注意,它不是标准C的一部分)。因此,在实践中,你可以摆脱它(转换void (*function)(void)void *和回void (*function)(void)),但严格说来,这不是由C标准规定。
乔纳森·莱夫勒

2
Jonathan和R .:这都很有趣,但是我敢肯定我们不会在这里打印函数指针,因此也许这不是讨论此问题的正确位置。我宁愿在这里看到一些支持,因为我坚持不使用%u
Kerrek SB 2012年

2
%u并且%lu所有机器上都是错误的,而不是某些机器。的规范printf非常清楚,当传递的类型与格式说明符要求的类型不匹配时,行为是不确定的。类型的大小是否匹配(取决于机器,是真还是假)无关紧要;这是必须匹配的类型,而它们永远不会匹配。
R .. GitHub停止帮助ICE

9

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


考虑#include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } 使用%u格式说明符打印变量地址是否为未定义行为?在大多数情况下,变量的地址为正,因此我可以使用%u代替%p吗?
析构

1
@Destructor:否,%uunsigned int类型的格式,不能与的指针参数一起使用printf
R .. GitHub停止帮助ICE

-1

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

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

这两个都是正确的。

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


1
欢迎来到SO。请花一些时间查看其他答案,它们清楚地说明了您忽略的许多细节。
AntoineL
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.