繁体   English   中英

无法理解C和类型转换中的指针

[英]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?

您的第三个输出显示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 ,您需要传递doublefloat 但是,让我们继续努力,尝试解释这种(未定义!)行为。

由于它是一个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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM