C语言 以十六进制表示的内存地址是否总是需要以“0x”开头?

pcww981p  于 2023-01-25  发布在  其他
关注(0)|答案(5)|浏览(268)

以十六进制表示的内存地址总是需要以'0x'开头吗?或者可以是其他的?条件是什么?

#include <stdio.h>

int main(void)
{
    int n = 50;
    int *p = &n;
    
    printf("%p\n", p);
}

这里我得到的输出是'00000000062FE14'。它不应该以0x开头吗?

voase2hg

voase2hg1#

%p的outlook的格式是由实现定义的,在gcc和clang(至少是www.example.com使用的版本)上tio.run,它似乎得到了一个0x前缀(十六进制数字a-f使用小写),但在编译器上却没有(而是使用A-F),这两种行为都是法律的的。
如果您希望代码的行为保持一致,则需要使用%x%X作为基本格式代码,这样您就可以精确地指定0x的包含,并且只指定一次。(总是足以表示该体系结构的任何指针值的固定数目的补零十六进制数字),你还需要显式地指定宽度。最终版本(确保你在任何64位指针架构上都能得到0x000000000062FE14)是:

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    int n = 50;
    int *p = &n;
    
    printf("0x%0*"PRIXPTR"\n", 2*(int)sizeof(p), (uintptr_t)p);
}

分解一下:

  • #include <inttypes.h>提供(通过stdint.huintptr_t的typedef,以及用于便携式打印它的宏
  • 0x是手动添加的前缀(因为#修饰符不会为零输入添加0x,我们甚至希望为NULL指针添加它)
  • 0*表示“用零填充到第一个参数的宽度”
  • PRIXPTR是一个宏,它为相对于uintptr_t的大写十六进制生成适当的格式代码(小写十六进制使用PRIxPTR
  • 2*(int)sizeof(p)被传递来匹配*的宽度,这允许我们计算出体系结构要求的数字大小,以打印相同固定宽度的该类型的任何指针。需要强制转换为int是因为*显式地期望int作为该参数,和sizeof生成size_ts;我相当肯定我可以依赖sizeof来获得一个指针,该指针返回一个适合int的值,所以强制转换是安全的。:-)
  • (uintptr_t)p强制转换为一个整数类型,足以容纳指向void的任何指针(这意味着它可以容纳指向对象类型的任何指针,但在POSIX之外,不能保证它可以容纳函数指针);x/X代码使用整数,而不是指针,因此它不能作为指针传递而不违反规范。

从技术上讲,对[u]intptr_t的支持是可选的(并且至少需要C99/C++11,但希望这不是问题)。但是我强烈怀疑不提供[u]intptr_t的系统的指针比 any 提供的整数类型大(它们将是uintmax_t可能小于指针中的位数的怪异系统,例如其中程序本机知道跨群集的存储器并且可以用可以引用非本地存储器的128位全局地址指针直接对其寻址的系统,但是处理器仍然是64位,编译器不支持将两个64位寄存器组合起来表示一个128位整数,因此uintmax_t太小,无法容纳指针地址),因此无论如何,您都无法对它们进行可移植处理(您将被%p卡住)。

tkclm6bt

tkclm6bt2#

%x有一个选项%#x,这意味着"0x"会被附加到输出中。但是%p没有指定这个选项,但是有一些方法可以安全地将指针转换成一个大整数,然后打印出来:

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    int n = 50;
    int *p = &n;
    printf("%"PRIxPTR "\n", (uintptr_t)p);
    printf("%#"PRIxPTR "\n", (uintptr_t)p);
}

输出沿着内容:

7ffce1c44c04
0x7ffce1c44c04
wi3ka0sx

wi3ka0sx3#

由于%p输出的格式是由C标准(参见7.21.6.1节fprintf函数)和POSIX(fprintf())“实现定义”的,因此不同的实现会有不同的处理方式。有些没有(有些可能使用0X,但我不记得见过这种用法),许多使用小写字母表示数字10-15;看起来你的实现使用了大写,这是不寻常的。有些实现用前导零填充;在macOS上,空指针打印为0x0,而其他指针打印的值类似0x7ffeebcf53bc,因此宽度不一定是固定的。
不要求实现之间具有一致性。如果您想要一致性,请使用类型uintptr_t和宏,例如<inttypes.h>中的PRIXPTR(或PRIxPTR)。

#include <assert.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>

static_assert(sizeof(void *) == sizeof(void (*)(void)),
              "Object pointers are not the same size as function pointers");

#ifndef PTR_WIDTH
#define PTR_WIDTH "12"
#endif

#define PTR_FORMAT "0x%." PTR_WIDTH PRIXPTR

int main(void)
{
    printf("Object pointers:\n");
    int i = 0;
    int *a = malloc(3 * sizeof(*a));
    int *p = (int *)4100;

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

    printf(PTR_FORMAT "\n", (uintptr_t)0);
    printf(PTR_FORMAT "\n", (uintptr_t)&i);
    printf(PTR_FORMAT "\n", (uintptr_t)a);
    printf(PTR_FORMAT "\n", (uintptr_t)p);

    printf("Function pointers:\n");
    printf("%p\n", (void *)(uintptr_t)main);
    printf("%p\n", (void *)(uintptr_t)printf);
    printf(PTR_FORMAT "\n", (uintptr_t)main);
    printf(PTR_FORMAT "\n", (uintptr_t)printf);

    free(a);
    return 0;
}

在Mac上,这将生成:

Object pointers:
0x0
0x7ffee094539c
0x7ffed9405a10
0x1004
0x000000000000
0x7FFEE094539C
0x7FFED9405A10
0x000000001004
Function pointers:
0x10f2bddd0
0x7fff205f30a8
0x00010F2BDDD0
0x7FFF205F30A8

你可能会看到不同的值,但输出应该是相似的。在Mac上,我从来没有见过超过12个十六进制数字的地址,所以我把PTR_WIDTH设置为12(作为一个字符串)。如果你想要64位系统的最大宽度,你可以把它设置为16(例如gcc -o pp29 -DPTR_WIDTH='"16"' pp29.c),或者如果你在32位系统上使用8
注意,在C语言中,不能直接将函数指针正式转换为对象指针,反之亦然:
§6.2.5类型§ 28:
指向void的指针应具有与指向字符类型的指针相同的表示和对齐要求。48)类似地,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求。2所有指向结构类型的指针应具有彼此相同的表示和对齐要求。所有指向联合类型的指针应该有相同的表示和对齐要求。指向其他类型的指针不需要有相同的表示或对齐要求。
48)相同的表示和对齐要求意味着函数的参数、函数的返回值以及联合体的成员的可互换性。
§6.3.2.3指针§ 6-8:
6任何指针类型都可以转换为整数类型。除非前面指定,否则结果是由实现定义的。如果结果不能用整数类型表示,则行为是未定义的。结果不需要在任何整数类型的值的范围内。
7一个指向一个对象类型的指针可以转换成指向另一个对象类型的指针。如果结果指针与被引用的类型没有正确对齐,则该行为是未定义的。否则,当再次转换回来时,结果应该与原来的指针相等。当一个指向一个对象的指针转换成指向一个字符类型的指针时,结果指向对象的最低地址字节。2结果的连续递增,直到对象的大小,产生指向对象剩余字节的指针。
8指向一种类型函数的指针可以转换为指向另一种类型函数的指针,然后再转换回来;结果应该与原始指针相等。2如果转换后的指针被用来调用一个类型与被引用类型不兼容的函数,则该行为是未定义的。
68)通常,“正确对齐”的概念是传递性的:如果指向类型A的指针与指向类型B的指针正确对齐,而指向类型B的指针又与指向类型C的指针正确对齐,则指向类型A的指针与指向类型C的指针正确对齐。
然而§6.2.3.2 ¶6提供了一个转义窗口--转换成一个合适的整型类型--但是要注意,理论上(尽管实践中很少),有些平台上没有整型类型可以保存函数指针,这就是为什么直接打印函数地址时会有两个连续的强制类型转换。
参见§7.20.1.4能够保存对象指针的整数类型:
1以下类型指定一个有符号整数类型,其属性是指向void的任何有效指针都可以转换为该类型,然后再转换回指向void的指针,结果将与原始指针进行比较:

intptr_t

以下类型指定一个无符号整数类型,其属性是指向void的任何有效指针都可以转换为该类型,然后再转换回指向void的指针,并且结果将与原始指针进行比较:

uintptr_t

这些类型是可选的。
如果uintptr_tintptr_t类型不可用,这将是一台非常不寻常的机器。

mwngjboj

mwngjboj4#

十六进制的内存地址是否总是需要以“0x”形式结束?

***回答:***不,他们没有。
来自C11:

p
参数应该是一个指向void的指针。指针的值以实现定义的方式转换为一个打印字符序列。
它是由实现定义的,指针必须强制转换为void *,否则代码将调用未定义的行为。

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

xtupzzrd5#

%p是“指针”的格式说明符,与其他一些解释不同,它是“依赖于实现的”,因此不能真正期望一致性,考虑到不同的环境可能具有不同的寻址语义,这是公平的

相关问题