C将地址转换为int的规则是什么?

时间:2015-12-31 06:57:40

标签: c

我是C的初学者,今天我正在研究指针部分,我发现我可以直接打印地址,当使用正确的类型escaper时,我甚至可以打印存储在该内存地址中的预期值。

后来,我做了一些实验:

##### CODE PART ######
#include <stdio.h>  // Define several variable types, macros, and functions about standard input/output.

int main () {
    char my_string[] = "address test";
    printf("%s\n", &my_string);
    printf("%p\n", &my_string);
    printf("%d\n", &my_string);
    printf("%x\n", &my_string);
    printf("\n");

    char *p = "pointer string test";
    printf("%s\n", p);
    printf("%p\n", p);
    printf("%d\n", p);
    printf("\n");

    char *p2 = 'p';
    printf("%c\n", p2);
    printf("%p\n", p2);
    printf("%d\n", p2);
    return 0;
}


##### OUTPUT #####
address test
0x7fff58778a7b
1484229243
58778a7b

pointer string test
0x107487f87
122191751

p
0x70
112

我不太了解%d格式输出的行为,但经过更多的观察和实验。我发现%d正在转换内存地址的十六进制值的一部分。

但是对于my_string的地址,它省略了0x7fff部分,对于p的地址,省略了0x10部分,对于p2,它省略了0x部分1}}部分。在我的认知中,0x是十六进制值的头号。

但是,在将内存地址转换为int时,如何知道C将省略多少位,就像在my_stringp的样本中一样?

PS:我的系统版本是OSX10.10

4 个答案:

答案 0 :(得分:4)

C标准(ISO / IEC 9899:2011)有关于在指针和整数之间进行转换的说法:

  

6.3转化次数

     

6.3.2.3指针

     

¶5整数可以转换为任何指针类型。除非之前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。 67)

     

¶6任何指针类型都可以转换为整数类型。除了之前指定的以外,结果是实现定义的。如果结果无法以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。

     

67)用于将指向整数或整数的指针转换为指针的映射函数旨在   与执行环境的寻址结构保持一致。

请注意,指针和整数之间的转换行为是实现定义的,而不是未定义的。但是,除非使用的整数类型为uintptr_tintptr_t(来自<stdint.h> - 或<inttypes.h>),否则您可能会看到截断效果,如果指针的大小和整数类型不匹配。如果在32位和64位系统之间移动代码,则会在某处遇到问题。

在您的代码中,您有64位指针(因为您使用的是Mac OS X 10.10,并且您需要明确指定-m32以获得32位版本,但您的结果与64位一致无论如何都是建立的)。当您使用printf()%d转换规范将这些指针传递给%x时,您要求printf()打印32位数量,因此它格式化为64位中的32位你通过的比特。行为不明确;你本身没有获得转换,但调用代码(在main()中)将64位指针推送到堆栈上,被调用代码(printf())读取32位数量堆栈。如果您要求对printf()的一次调用打印多个值(例如printf("%d %x\n", p, p);),您就会得到更令人惊讶的结果。

您应该使用以下选项进行编译:

gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
    -Wold-style-definition -Werror …

使用这些选项,您的代码将无法编译;编译器会抱怨格式字符串和传递的值之间的不匹配。当我将代码保存到文件noise.c并使用clang(来自XCode 7.2,在Mac OS X 10.10.5上运行)编译时,我得到了:

$ /usr/bin/clang -O3 -g  -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror noise.c -o noise
noise.c:5:20: error: format specifies type 'char *' but the argument has type 'char (*)[13]'
      [-Werror,-Wformat]
    printf("%s\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:7:20: error: format specifies type 'int' but the argument has type 'char (*)[13]' [-Werror,-Wformat]
    printf("%d\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:8:20: error: format specifies type 'unsigned int' but the argument has type 'char (*)[13]'
      [-Werror,-Wformat]
    printf("%x\n", &my_string);
            ~~     ^~~~~~~~~~
noise.c:14:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%d\n", p);
            ~~     ^
            %s
noise.c:17:11: error: incompatible integer to pointer conversion initializing 'char *' with an expression of
      type 'int' [-Werror,-Wint-conversion]
    char *p2 = 'p';
          ^    ~~~
noise.c:18:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%c\n", p2);
            ~~     ^~
            %s
noise.c:20:20: error: format specifies type 'int' but the argument has type 'char *' [-Werror,-Wformat]
    printf("%d\n", p2);
            ~~     ^~
            %s
7 errors generated.
$

编写严格的警告,并注意这些警告。

答案 1 :(得分:3)

没有&#34;十六进制值&#34;这样的东西。数字是一个数额。十进制和十六进制只是使用不同约定的数字的表示。也可以使用罗马数字表示数字,其值仍然保持不变。

变量的地址是概念,而不是物理的东西。它通常恰好是当前操作系统和CPU架构上的一个(大)数字,但这并不是一成不变的。

根据编译器及其编译的代码,变量可以存储在内存中(它的地址看起来像一个大整数)或不存在。编译器可以优化代码并将临时变量存储在CPU寄存器中;在这种情况下,它没有地址。

返回您的代码。 &my_string是变量my_string的地址。它看起来像一个数字。您可能在64位处理器上运行代码。这种情况下的内存地址是64位无符号数。

  • printf("%p\n", &my_string); - 打印一个64位无符号数字(最适合您正在使用的硬件架构上的指针表示)。
  • printf("%d\n", &my_string); - 您将64位数字传递给printf(),但由于%d specifier,它认为该值为32位。它只捕获传递值的一半(8个字节中的4个)并将其表示为有符号整数。但是那一半?它取决于代码运行的体系结构。 此代码的行为未定义。
  • printf("%x\n", &my_string); - 与%d类似,它使用十六进制表示法仅打印(相同)传递值的一半。 此代码的行为再次未定义。

0x前缀不是十六进制表示的一部分;它只是一个标记,它向C编译器发出信号,表示十六进制表示中的数字如下。虽然十六进制表示是通用的,但不同的语言使用different ways to encode them。甚至C语言也使用两种不同的标记; 0x用于为数字添加前缀,\x用于为a character的十六进制表示添加前缀。

答案 2 :(得分:2)

没有规则。这不属于C标准。您的代码会导致undefined behaviour。您在整个程序中观察到的任何结果都是毫无意义的。

使用printf,您必须自己将参数转换为正确的类型。

答案 3 :(得分:2)

printf("%d\n", &my_string);
printf("%x\n", &my_string);

导致未定义的行为。格式说明符和参数类型必须匹配printf才能正常工作。有关它们适用的有效格式说明符和数据类型的列表,请查看http://en.cppreference.com/w/c/io/fprintf

以下几行遇到同样的问题。

printf("%d\n", p);

printf("%c\n", p2);
printf("%d\n", p2);

该行

char *p2 = 'p';

将表示字符'p'的整数值指定给p2。但是,这不是有效的地址。

可用于保存指针的整数类型为intptr_tuintptr_t。因此,您可以使用:

char my_string[] = "address test";
intptr_t ptr = &my_string;

但是,您无法使用%d格式说明符来打印该值。您需要使用:

printf("%" SCNdPTR "\n", ptr);

打印出来。

请查看http://en.cppreference.com/w/c/types/integer了解详情。