簡體   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