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