【问题标题】:Correct format specifier to print pointer or address?正确的格式说明符以打印指针或地址?
【发布时间】:2023-03-20 09:20:01
【问题描述】:

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

%u - 无符号整数

%x - 十六进制值

%p - 空指针

打印地址的最佳格式是什么?

【问题讨论】:

    标签: c pointers format memory-address


    【解决方案1】:

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

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

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

    【讨论】:

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

    使用%p,作为“指针”,不要使用其他任何东西*。标准不能保证您可以将指针视为任何特定类型的整数,因此您实际上会在整数格式中获得未定义的行为。 (例如,%u 需要 unsigned int,但如果 void* 的大小或对齐要求与 unsigned int 不同怎么办?)

    *) [请参阅 Jonathan 的最佳答案!] 作为 %p 的替代方法,您可以使用来自 <inttypes.h> 的特定于指针的宏,在 C99 中添加。

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

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

    【讨论】:

    • 所有 object 指针都可以转换为void *(尽管对于printf(),您在技术上需要显式转换,因为它是一个可变参数函数)。函数指针不一定可以转换为void *
    • @caf:哦,我不知道可变参数 - 已修复!谢谢!
    • 标准 C 不要求函数指针可以转换为 void * 并返回到函数指针而不会丢失;幸运的是,POSIX 确实明确要求这样做(注意它不是标准 C 的一部分)。因此,在实践中,您可以摆脱它(将 void (*function)(void) 转换为 void * 并返回到 void (*function)(void)),但严格来说,它不是 C 标准的强制要求。
    • Jonathan 和 R.:这一切都很有趣,但我很确定我们不会在这里打印函数指针,所以也许这里不是讨论这个问题的正确地方。我宁愿在这里看到一些支持,因为我坚持不使用%u
    • %u%lu所有机器上都是错误的,而不是在某些机器上。 printf 的规范非常明确,当传递的类型与格式说明符要求的类型不匹配时,行为未定义。类型的大小是否匹配(可能是真或假,取决于机器)是无关紧要的;这是必须匹配的类型,而且它们永远不会匹配。
    【解决方案3】:

    假设您不介意不同平台之间格式的变幻莫测和变化,最简单的答案是标准的%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 在 cmets 中提问:

    我对标准促销和可变参数有点困惑。是否所有指针都被标准提升为 void*?否则,如果int* 是两个字节,void* 是 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 *)。哦,好吧,SO 有利于让您检查您的假设。

    C 标准明确地不要求函数指针与对象指针的大小相同。这对于不破坏类 DOS 系统上的不同内存模型是必要的。在那里你可以有 16 位数据指针,但有 32 位函数指针,反之亦然。这就是为什么 C 标准不强制要求函数指针可以转换为对象指针,反之亦然。

    幸运的是(对于以 POSIX 为目标的程序员),POSIX 进入了漏洞并确实要求函数指针和数据指针的大小相同:

    §2.12.3 Pointer Types

    所有函数指针类型都应与指向 void 的类型指针具有相同的表示。将函数指针转换为void * 不应改变表示。通过这种转换产生的void * 值可以使用显式转换转换回原始函数指针类型,而不会丢失信息。

    注意: ISO C 标准不要求这样做,但符合 POSIX 要求。

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

    【讨论】:

    • 我对标准促销和可变参数有点困惑。是否所有指针都被标准提升为void*?否则,如果int* 是两个字节,void* 是 4 个字节,那么从参数中读取四个字节显然是错误的,不是吗?
    • 请注意,对 POSIX (POSIX 2013) 的更新已删除第 2.12.3 节,将大部分要求移至 dlsym() 函数。有一天我会写下变化……但是“一天”不是“今天”。
    • 这个答案是否也适用于指向函数的指针?它们可以转换为void * 吗?嗯,我看到你的评论here。既然只需要一瓦转换(函数指针指向void *),那么它可以工作吗?
    • @chux:严格来说,答案是“否”,但实际上答案是“是”。 C 标准不保证函数指针可以转换为void * 并返回而不会丢失信息。实际上,很少有机器函数指针的大小与对象指针的大小不同。我不认为该标准提供了一种在转换有问题的机器上打印函数指针的方法。
    • “并返回而不丢失信息”与打印无关。这有帮助吗?
    【解决方案4】:

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

    【讨论】:

    • 考虑#include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } 使用%u格式说明符打印变量地址是否未定义行为?在大多数情况下,变量地址是正数,所以我可以使用%u而不是%p ?
    • @Destructor: 不,%uunsigned int 类型的格式,不能与指向printf 的指针参数一起使用。
    【解决方案5】:

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

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

    这两个都是正确的。

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

    【讨论】:

    • 欢迎来到 SO。请花一些时间查看其他答案,它们清楚地解释了您忽略的许多细节。
    猜你喜欢
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    相关资源
    最近更新 更多