[英]Unable to understand pointers in C and typecasting
我不明白为什么第三和第四printf
给出54和-61。 据我说,该程序应该给出0作为输出,因为预期字符指针最多显示(sizeof(char) * 8)
位的输出值,而二进制54则为00000000 00110110
。
#include<stdio.h>
void main()
{
int i=54;
float a=3.14;
char *ii,*aa;
ii=(char *)&i;
aa=(char *)&a;
printf("%u\n",ii);
printf("%u\n",aa);
printf("%d\n",*ii);
printf("%d\n",*aa);
}
编辑:第四个printf
(如果我在那里使用%f
,我误输入%d
)给出0.00000
。 为什么?
您的第三个输出显示54,因为在您的计算机上,
int i=54;
像这样存储在内存中:
36 00 00 00
您的指针指向此处:
36 00 00 00
^^
因此,当您将0x36打印为char
(一个字节长的整数类型)时,会看到54。
这种存储格式称为“ little endian ”,并在非常普遍的x86和amd64处理器上使用。
注意,该语言不能保证以这种方式存储整数。 您可能会在其他机器或编译器上获得不同的结果。 不要依赖它。
float
工作原理类似,但显示起来却复杂得多。 同样,这完全取决于机器。 对于amd64,如果您在IEEE单机中编码3.14
(这取决于平台),然后向后存储四个字节(至少,我相信amd64将它们存储为“ little endian”,尽管我不确定为什么,因为它是一个浮点数。),第一个插槽中的字节值当被视为带符号的8位二进制补码整数(这也取决于平台)时,应该算出您所看到的值。
最后,您说:
我不知道小爱丁。 但这不是浮点数。 如果我在第四位使用%f代替%d,则给出0.000000000(错误地我在这里键入了%d)
我假设你的意思是:
printf("%f\n",*aa);
而且那个aa
仍然是char *
。 这格式不正确:对于%f
,您需要传递double
或float
。 但是,让我们继续努力,尝试解释这种(未定义!)行为。
由于它是一个char *
,当您取消引用它时,在您的计算机上,它可能会读取一些一字节的值。 3.14
作为小端浮点数是:
c3 f5 48 40
^^
0xc3
是一个带二进制补码的1字节整数,它是-61,它解释了您的问题。 因此,对于您的程序, *aa
是-61。 当您将其传递给printf
,它将被提升为int
,因为printf
是一个“ varargs”(可变参数数量)函数。 在某些编译器中进行编译时,您会看到以下信息:
prog1.c:14:7:警告:格式'%f'期望类型为'double'的参数,但是参数2的类型为'int'[-Wformat]
因此,“ int”将以您的平台使用的任何方式传递给printf
。 让我们调查一下。 为了明确起见,我正在编译以下内容:
#include<stdio.h>
int main()
{
int i=54;
float a=3.14;
char *ii,*aa;
ii=(char *)&i;
aa=(char *)&a;
printf("%u\n",ii);
printf("%u\n",aa);
printf("%d\n",*ii);
printf("%f\n",*aa);
return 0;
}
我做:
% gcc -g -o prog1 prog1.c
prog1.c: In function ‘main’:
prog1.c:11:2: warning: format ‘%u’ expects argument of type ‘unsigned int’, but argument 2 has type ‘char *’ [-Wformat]
prog1.c:12:2: warning: format ‘%u’ expects argument of type ‘unsigned int’, but argument 2 has type ‘char *’ [-Wformat]
prog1.c:14:2: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat]
(以防万一: gcc
在这里抛出了非常好的警告:它指出了程序中未定义的行为(错误)。您应该始终修复这些错误。我们将忽略它们进行调查,但是请注意编译器可以真正做到这一点,因此,下面的所有内容都无法保证。)
然后,让我们启动这是一个调试器,然后停止最后一个printf。 对我来说,是第14行。因此:
% gdb prog1
GNU gdb (Gentoo 7.6.2 p1) 7.6.2
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.gentoo.org/>...
Reading symbols from /home/me/code/random/prog1...done.
(gdb) break prog1.c:14
Breakpoint 1 at 0x4005db: file prog1.c, line 14.
让我们运行到那个断点。
(gdb) r
Starting program: /home/me/code/random/prog1
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
4294959628
4294959624
54
Breakpoint 1, main () at prog1.c:14
14 printf("%f\n",*aa);
现在我们停在“ printf
”上,那是什么意思呢? 让我们看一些汇编器!
(gdb) disassemble
Dump of assembler code for function main:
0x000000000040056c <+0>: push %rbp
0x000000000040056d <+1>: mov %rsp,%rbp
0x0000000000400570 <+4>: sub $0x20,%rsp
0x0000000000400574 <+8>: movl $0x36,-0x14(%rbp)
0x000000000040057b <+15>: mov 0x12f(%rip),%eax # 0x4006b0
0x0000000000400581 <+21>: mov %eax,-0x18(%rbp)
0x0000000000400584 <+24>: lea -0x14(%rbp),%rax
0x0000000000400588 <+28>: mov %rax,-0x8(%rbp)
0x000000000040058c <+32>: lea -0x18(%rbp),%rax
0x0000000000400590 <+36>: mov %rax,-0x10(%rbp)
0x0000000000400594 <+40>: mov -0x8(%rbp),%rax
0x0000000000400598 <+44>: mov %rax,%rsi
0x000000000040059b <+47>: mov $0x4006a4,%edi
0x00000000004005a0 <+52>: mov $0x0,%eax
0x00000000004005a5 <+57>: callq 0x400450 <printf@plt>
0x00000000004005aa <+62>: mov -0x10(%rbp),%rax
0x00000000004005ae <+66>: mov %rax,%rsi
0x00000000004005b1 <+69>: mov $0x4006a4,%edi
0x00000000004005b6 <+74>: mov $0x0,%eax
0x00000000004005bb <+79>: callq 0x400450 <printf@plt>
0x00000000004005c0 <+84>: mov -0x8(%rbp),%rax
0x00000000004005c4 <+88>: movzbl (%rax),%eax
0x00000000004005c7 <+91>: movsbl %al,%eax
0x00000000004005ca <+94>: mov %eax,%esi
0x00000000004005cc <+96>: mov $0x4006a8,%edi
0x00000000004005d1 <+101>: mov $0x0,%eax
0x00000000004005d6 <+106>: callq 0x400450 <printf@plt>
=> 0x00000000004005db <+111>: mov -0x10(%rbp),%rax
0x00000000004005df <+115>: movzbl (%rax),%eax
0x00000000004005e2 <+118>: movsbl %al,%eax
0x00000000004005e5 <+121>: mov %eax,%esi
0x00000000004005e7 <+123>: mov $0x4006ac,%edi
0x00000000004005ec <+128>: mov $0x0,%eax
0x00000000004005f1 <+133>: callq 0x400450 <printf@plt>
0x00000000004005f6 <+138>: mov $0x0,%eax
0x00000000004005fb <+143>: leaveq
0x00000000004005fc <+144>: retq
那是main
,箭头( =>
)在这里。 call
指令0x00000000004005f1
是对第四个printf
的调用,如您所见,调用它需要一些设置:所有这些mov
指令。 由于他们设置了调用,而我们感兴趣的是传递给printf
,因此我们需要让它们运行,因此我们需要按照该call
指令逐步执行程序。 我们可以使用另一个断点来做到这一点:
(gdb) break *0x00000000004005f1
Breakpoint 2 at 0x4005f1: file prog1.c, line 14.
(gdb) continue
Continuing.
Breakpoint 2, 0x00000000004005f1 in main () at prog1.c:14
14 printf("%f\n",*aa);
现在,我们在该call
声明中。 现在,因为我使用的是amd64芯片(Intel Core i7。有时也称为x86-64。),而我没有运行Windows,对我来说,我们通过将参数从左到右调用函数。对,进入某些寄存器。 从右边开始,第一个参数是*aa
,请记住,我们已将其设置为-61。 我们可以通过以下方式转储我们的寄存器:
(gdb) info all-registers
rax 0x0 0
rbx 0x0 0
rcx 0x2 2
rdx 0x7ffff7dd7820 140737351874592
rsi 0xffffffc3 4294967235
rdi 0x4006ac 4196012
rbp 0x7fffffffe220 0x7fffffffe220
rsp 0x7fffffffe1f8 0x7fffffffe1f8
r8 0x2 2
r9 0x7ffff7dd4640 140737351861824
r10 0x7fffffffe0d8 140737488347352
r11 0x246 582
r12 0x400480 4195456
r13 0x7fffffffe300 140737488347904
[ snip … ]
ymm0 {v8_float = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_double = {0x0, 0x0, 0x0, 0x0}, v32_int8 = {0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0,
0xff, 0x0, 0x0, 0x0, 0xff, 0x0 <repeats 19 times>}, v16_int16 = {0x0, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
v8_int32 = {0x0, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0}, v4_int64 = {0xff00000000, 0xff000000ff, 0x0, 0x0}, v2_int128 = {0x000000ff000000ff000000ff00000000,
0x00000000000000000000000000000000}}
因为-61是整数,所以它以整数寄存器结尾,在这里,我们可以看到它在rsi
。 (已经过符号扩展,这就是为什么它在4个字节中不是0xffffffc3
:-61,而不是一个字节。)但是, %f
是一个浮点,很可能会读取一个浮点寄存器,例如本机上的ymm0
。 它恰好是零。 这不一定是正确的,因为这是未定义的行为,但是确实如此,因此我们将得到零。
¹除了病态的好奇心外,这不是您经常关心的事情之一。
²我唯一无法解释的部分是为什么我们的整数以rsi
结尾。 我觉得应该在rdi
。 就像我说的,病态的好奇心。 ( 编辑: gh,诅咒我的好奇心。它以rdi
结尾,因为rdi
用于第二个参数,它是第二个参数。Wikipedia将其标记为“从右到左”,但这仅适用于堆栈中的内容:寄存器从左到右分配。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.