【问题标题】:Why doesn't %p show the full width of pointer?为什么 %p 不显示指针的全宽?
【发布时间】:2017-01-13 16:17:19
【问题描述】:

如果我没记错的话,在 64 位机器上,指针相当于 02^64-1 之间的数字。因此得到以下结果:

printf("%p",    (void*)     -1); → 0xffffffffffffffff
printf("0x%lx", (uintptr_t) -1); → 0xffffffffffffffff

然而,当我从刚刚分配的对象中打印指针时,结果显示的宽度不同:

printf("%p", (void*) &myobject); → 0x70d940

有什么理由为什么我的所有对象(总是)在内存空间的开头都有一个地址?多亏了虚拟内存,可以(从程序的角度来看)基本上可以放置在 64 位空间中的任何位置。

另外,为什么%p 默认不打印全宽? (我希望表现得像0x%016lx

编辑:另外,0x018p 无效有什么原因吗?

error : fanion « 0 » used with « %p » gnu_printf format

【问题讨论】:

  • %p 可以用它选择的任何方式格式化地址。它不必是十六进制的(尽管通常是);它不必以0x0X 开头;它可能会以大写或小写形式打印十六进制。尝试printf("%p\n", (void *)0) — 在 Mac 上,这只是0,即使其他值以0x1234 样式打印。试试printf("%p\n", (void *)1024);——它可能会打印一个短地址0x400
  • 为什么你认为它不是全宽?反正也没办法控制。这是一个有效值。想想正常情况,我们写“循环从1到100”,我们不写,“循环从(001)10到(100)10”,对吧?
  • 我希望能像0x%016lx那样行事你的期望是基于什么?
  • @SouravGhosh:我不知道,这是我的问题。
  • @Amxx:我真的不知道还能告诉你什么。从字面上看,您想要输出的任何数字(本机类型)都是固定宽度(对于给定的架构),但我们不会无缘无故地给它们全部前导零。当您无法自己解释期望时,基于错误的期望来回答问题是非常困难的!

标签: c++ c memory


【解决方案1】:

如评论中所述,

%p 可以用它选择的任何方式格式化地址。它不必是十六进制的(尽管通常是);输出不必以0x0X 开头;它可能会以大写或小写形式打印十六进制。

尝试printf("%p\n", (void *)0); — 在 Mac 上,它只打印 0,即使其他值以 0x1234 样式打印。试试printf("%p\n", (void *)1024);——它可能会打印一个短地址0x400

标准(POSIX 或 C 或 C++)中没有规定 %p 的输出应该是什么样子。

如果您想控制指针的格式,请使用 uintptr_t 和来自 <inttypes.h>PRIzPTR 宏:

void *vp = 0;
printf("0x%.16" PRIXPTR " contains 0x%" PRIXPTR "\n",
       (uintptr_t)&vp, (uintptr_t)vp);

如果您更喜欢小写十六进制数字,请使用PRIxPTR。我没有,而且我确实喜欢 0x 前缀,所以当我不受其他人有问题的审美限制时,我会使用它。

【讨论】:

    【解决方案2】:

    通过%p 格式说明符精确表示的指针是实现定义的。

    来自C standard 的第 7.21.6.1 节:

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

    特别是在 Linux 上,man page 声明如下:

    void * 指针参数以十六进制打印(就像 %#x%#lx)

    所以如果你在 Linux 上,你可能可以使用%#016lx

    但是,这只是一种实现,因此您不能对符合标准的 C 应用程序中的格式做出任何假设。

    例如,在 DOS 上运行的 Turbo C 上,一个指针实际上包含两个值,一个段和一个偏移量。

    【讨论】:

      【解决方案3】:

      主要从 C 的角度讲,因为您已标记 [C] 并且您正在使用 C 风格的代码。

      如果我没记错的话,在 64 位机器上,指针相当于 0 到 2^64-1 之间的数字。

      嗯,这有点棘手。指针可以转换为整数并返回,但未指定整数类型能够支持所有可能的往返指针-整数-指针转换而不会丢失数据所需的大小。实现不需要提供这种类型。

      此外,指针不需要表示为平面地址空间的整数索引,事实上,从历史上看,一些实现使用了不同的表示。因此,无论范围如何,断言指针等价于数字是不正确或不安全的。

      还要注意这...

      printf("0x%lx", (uintptr_t) -1);
      

      ... 是不安全的,因为它假定uintptr_tunsigned long int,但决不能保证是这种情况。为uintptr_t 类型的值编写格式字符串的安全方法是:

      printf("0x%" PRIxPTR, (uintptr_t) -1);
      

      (使用inttypes.h

      有什么理由为什么我的所有对象(总是)在内存空间的开头都有一个地址?多亏了虚拟内存,可以(从程序的角度来看)基本上可以放置在 64 位空间中的任何位置。

      这远远超出了 C 或 C++ 指定的任何内容,但是是的,在虚拟内存机器上,您的对象可以放置在系统虚拟地址空间中的任何位置。然而,他们为什么要这样呢?如果您的对象是动态分配的或具有自动持续时间,那么它们很可能被分配在相对一致的区域中,因为系统具有用于这些目的的区域。如果对象具有静态持续时间,那么原则上它们可能会受到ASLR 的约束,但这取决于系统、C 实现、编译选项和其他细节。

      另外,为什么 %p 默认不打印全宽? (我希望如果表现得像 0x%016lx)

      为什么要这样做?你的期望是不合理的。 printf() 通常不会打印前导零,除非您明确要求它这样做。 C 甚至不要求将输出格式化为十六进制数字——该格式完全由实现定义。

      编辑:另外,0x018p 无效有什么原因吗?

      因为标准明确指出0 标志字符与p 转换说明符一起使用时会导致未定义的行为。更一般地说,当与diouxXXaAe 以外的任何转换说明符一起使用时,它会产生 UB EfFgG。这是一长串说明符,但p 不在其中。如果您仔细看,您会发现所有这些都指定了转换为数字格式,而 C 确实 指定 p 来执行(尽管某些实现可能会这样指定)。

      【讨论】:

        【解决方案4】:

        这是因为在大多数 C++ 实现中,指针打印 (%p) 会抑制前导零以使内容更具可读性。您可以在打印时使用格式化技巧来获得完整的地址宽度。

        编辑:我提到的“格式化技巧”:

        std::cout
        << "0x"
        << std::hex
        << std::noshowbase
        << std::setw(2)
        << std::setfill('0')
        << n
        << std::endl ;
        

        取自https://stackoverflow.com/a/30326177/7407065

        【讨论】:

        • 格式化技巧在这里如何应用?能详细点吗?
        • 这是一个评论而不是一个答案。你不能评论吗?那么你需要先赢得一些声誉。 BTW:什么是格式化技巧?
        • @MichaelWalz 看起来它回答了我的问题。你为什么不认为这有资格作为答案?
        • @JeremyFriesner 这可能是一个原因,但不一定是一个原因。不过我没有投反对票。
        • @JeremyFriesner 不想粗鲁,但在这方面,“修复你的代码” 也可以是一个答案,对吧? 什么技巧? 是缺少的答案的基本部分。 :)
        【解决方案5】:

        另外,为什么 %p 默认不打印全宽?

        默认打印指针的全宽会破坏printf() 的样式。一种 “全宽”选项可通过修饰符获得。

        各种未修改的说明符打印最小文本。

        printf("%d", INT_MIN);
        // "-2147483648"   example
        printf("%d", 123);
        // "123", not "+0000000123"
        
        double value_nearest_one_seventh = 1/7.0;
        printf("%f", value_nearest_one_seventh);
        // "0.142857" 
        // instead of its exact value of
        // 0.142857142857142849212692681248881854116916656494140625
        

        如果代码需要打印指向固定宽度的指针,请使用长度修饰符

        void *ptr = ptr;
        int pointer_print_width = sizeof("0xffffffffffffffff") - 1;
        printf("<%*p>", pointer_print_width, ptr);
        // <          0x28cc6c>
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-17
          • 1970-01-01
          • 2020-12-09
          • 2017-07-02
          • 1970-01-01
          相关资源
          最近更新 更多