我是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_string
和p
的样本中一样?
PS:我的系统版本是OSX10.10
答案 0 :(得分:4)
C标准(ISO / IEC 9899:2011)有关于在指针和整数之间进行转换的说法:
6.3转化次数
6.3.2.3指针
¶5整数可以转换为任何指针类型。除非之前指定,否则结果是实现定义的,可能未正确对齐,可能不指向引用类型的实体,并且可能是陷阱表示。 67)
¶6任何指针类型都可以转换为整数类型。除了之前指定的以外,结果是实现定义的。如果结果无法以整数类型表示,则行为未定义。结果不必在任何整数类型的值范围内。
67)用于将指向整数或整数的指针转换为指针的映射函数旨在 与执行环境的寻址结构保持一致。
请注意,指针和整数之间的转换行为是实现定义的,而不是未定义的。但是,除非使用的整数类型为uintptr_t
或intptr_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_t
和uintptr_t
。因此,您可以使用:
char my_string[] = "address test";
intptr_t ptr = &my_string;
但是,您无法使用%d
格式说明符来打印该值。您需要使用:
printf("%" SCNdPTR "\n", ptr);
打印出来。