如果我将 printf 方法中的所有 %X 替换为 %p 会怎样

What if I replace all the %X to %p in printf method

%X用于打印十六进制 %p用于打印指针,十六进制

我知道使用 %x 而不是 %p 可能会导致十六进制值被截断,从而导致不准确。

当使用 %p 而不是 %x 打印十六进制时,唯一的缺点是 MSB 处有大量的 0。

那么,在任何情况下我都不能使用 %p 来代替 %x

%x 格式说明符需要一个 unsigned int 作为参数。指针的大小或表示可能与 unsigned int(或 unsigned longunsigned long long)不同。使用错误的格式说明符会调用 undefined behavior,必须避免。

此外,并非所有指针都必须具有相同的表示形式,因此在使用 %p.

打印时,您需要将有问题的指针显式转换为 void *

"What if I replace all the %X to %p in printf() method?"

%p 仅用于指向 (void *) 的指针。如果您传递一个与 %p 相关的参数,该参数不是指针并且严格来看也不是转换为 void * 的指针,那么程序将调用 undefined behavior.

p - The argument shall be a pointer to void. The value of the pointer is converted to a sequence of printing characters, in an implementation-defined manner.

Source: C18, §7.21.6.1/8 - "the fprintf function".


"So, is there any case where I cannot use %p to substitute the %x?"

事实上,在大多数情况下你不能这样做。唯一的情况是当您错误地将 void* 类型的参数传递给 %x 格式说明符时。然后,您可以通过将 %x 替换为 %p.

来修复您的旧代码变得正确和正确

您必须首先完全理解为什么如果您尝试使用 %x 格式说明符打印地址,这些值会被截断。

如果您将地址传递给 printf,将会有一定数量的字节传递给该函数。大小由 sizeof(void*) 定义。 如果您随后告诉 printf 您想要格式化为十六进制格式的 int 值(通过使用 %x),那么 printf 将只读取相关的字节数到 int 参数,即 sizeof(int)。 (实际上,类型 unsigned int 的值是预期的。但这与类型 int 具有相同的大小,并且当将值传递给类型 int 或更小的 printf 时,它将只转换为 int 类型。)如果这些值不匹配,将在转换下一个参数时提取和解释一些多余的字节。或者可用字节较少,printf 将读取无效内存。

如果你在相反的方向作弊 printf,也会出现同样的问题。然后你将只传递与 int 相关的字节数,但 printf 将获取指针中的字节数。这又会造成麻烦。

因此只需使用正确的格式说明符即可。

顺便说一句: %p 的格式不一定以十六进制格式完成。如果你有一些 CPU 架构,其中指针被分割成 page/offset 个值,你可能会得到类似 ff80:0010 的东西,而不是单个十六进制数。

通过 %p 格式化数据的方式是 implementation-defined。例如,当您使用 %p:

创建 hex-dumping 软件时,您可能会得到难看的结果

例如,运行这个,

#include <stdio.h>

int main(void) {
    char data[64];
    /* read data from file in real application */
    for (int i = 0; i < 64; i++) data[i] = (char)i;

    puts("--- %x version ---");
    for (int i = 0; i < 64; i++) {
        printf(" %02X", (unsigned char)data[i]);
        if ((i + 1) % 16 == 0) putchar('\n');
    }

    puts("--- %p version ---");
    for (int i = 0; i < 64; i++) {
        printf(" %p", (void*)(unsigned char)data[i]);
        if ((i + 1) % 16 == 0) putchar('\n');
    }

    return 0;
}

您可以 get this:

--- %x version ---
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F
 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
--- %p version ---
 (nil) 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf
 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f
 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2a 0x2b 0x2c 0x2d 0x2e 0x2f
 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x3a 0x3b 0x3c 0x3d 0x3e 0x3f

在这个实验中,我不想要的 0x 前缀被添加到 %p 版本中,并且 0 被打印为 (nil)%p.

不要使用 %p 打印 non-pointer 类型的值。不要使用 %x 打印指针类型的值。如果参数的类型与转换说明符的预期不匹配,则行为是 undefined 并且输出可以是任何字面意思。

%p的输出是implementation-defined;没有标志来控制其格式。

当使用 %p printf 时,会将变量打印为 unsigned long long(8 字节),而当使用 %x printf 时,会将变量视为 unsigned int(4 字节),因此您会注意到没有区别使用 %p 而不是 %x 额外的字节将只用零归档,但反过来使用 %x 而不是 %p 它将省略额外的字节并将变量视为 4 个字节