函數指針的轉換,調用轉換後的指針的結果如何,請問Visual Studio是如何實現的?
前提是我看了TCPL上的一句話,然後做了測試,不明白VS是如何實現的。
英文版: a pointer to a function may be converted to a pointer to another function type. Calling the function specified by the converted pointer is implementation-dependent; however, if the converted pointer is reconverted to its original type, the result is identical to the original pointer.中文版: 指向一個函數的指針可以轉換為指向另一個函數的指針。調用轉換後指針所指的 函數的結果依賴於具體的實現。但是,如果轉換後的指針被重新轉換為原來的類型,則結果與原來的指針一致。測試1:
#include &
int put_int(void);
float put_float(void);int main()
{
int (*pf)(void) = put_int;
float (*pf2)(void) = (float (*)(void))pf;printf("%d, %f", pf(), pf2());
system("pause");
return 0;
}int put_int(void)
{
return -858993460;
}// 可以沒有put_float函數,也能編譯執行,結果為 -858993460, -1.#IND00
// 如果有put_float函數,不管返回-1.0還是-2.0,結果依然是 -858993460, -1.#IND00
float put_float(void)
{
return -1.0;
}
可以沒有put_float函數,也能編譯執行,結果為 -858993460, -1.#IND00
如果有put_float函數,不管返回-100還是-2000,結果依然是 -858993460, -1.#IND00測試2:
#include &
int put_int(void);
float put_float(void);int main()
{
float (*pf)(void) = put_float;
int (*pf2)(void) = (int (*)(void))pf;printf("%f, %d", pf(), pf2());
getchar();
return 0;
}float put_float(void)
{
return -1.0;
}// 可以沒有put_int函數,也能編譯執行,結果為 -1.000000, -858993460
// 如果有put_int函數,不管返回-123456789還是-987654321,結果依然是 -1.000000, -858993460
int put_int(void)
{
return -123456789;
}
可以沒有put_int函數,也能編譯執行,結果為 -1.000000, -858993460
如果有put_int函數,不管返回-123456789還是-987654321,結果依然是 -1.000000, -858993460__________________________________大家可以看出 test2 的%d的列印值與 test1 的有關係,可是我新建了一個解決方案呀,搞不懂。同時我在兩個在線編譯網站上執行 test2 結果分別為:http://codepad.org: -1.000000, 0http://ganquan.info:-1.000000, 1
其實這個問題和這個例子很像:
#include &
#include &
using namespace std;
int put_int(void);
float put_float(void);
int main()
{
int (*pf)(void) = put_int;
float (*pf2)(void) = (float (*)(void))pf;
int x = 37;
float f = *(float *)x;
cout &<&< pf2() &<&< endl; cout &<&< f &<&< endl; return 0; } int put_int(void) { return 37; } float put_float(void) { return -1.0; }
原因在於int 和 float 的數字表示不一樣,Floating point斯坦福大學公開課:編程範式 37分鐘開始你問這個問題的具體疑問是什麼,一個類型的函數指針不能被另一個類型的函數指針使用,這一點你是已經知道的,然後你問VS是怎麼實現的是怎麼回事呢?
你是不是想知道:僅僅就是返回值不一樣,為什麼VS會實現成這個結果不一樣?
我沒有時間去調查,但可以給一個顯而易見的可能性:編譯器對你的程序進行翻譯,是基於局部的信息的,你在調用函數的地方,給了一個函數類型,說你調用了一個函數,返回值是int,它會在嘗試在eax或者r1上取返回值,如果你反過來,它就有可能在st0之類的地方取返回值了。
你非要改變函數的類型來使用,結果在不同的平台,編譯器,乃至版本上,運行實例上,都是可以不一樣的。
我拿Linux@x86_64試了一下,你可以明顯看到,兩個返回值,一個用的rax, 一個用的xmm0,根本就不是一個值,哪裡有什麼解釋這回事啊。 pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $32, %rsp
movq $put_int, -16(%rbp)
movq -16(%rbp), %rax
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
call *%rax
cvtss2sd %xmm0, %xmm1
movsd %xmm1, -24(%rbp)
movq -16(%rbp), %rax
call *%rax
movsd -24(%rbp), %xmm0
movl %eax, %esi
movl $.LC0, %edi
movl $1, %eax
call printf
movl $0, %eax
leave
.cfi_def_cfa 7, 8
ret
@專治高級噴子
說的基本符合事實。就是浮點數返回值,根本不是通過 eax 寄存器傳遞給調用方的。
CPU 上有集成 FPU(浮點單元),你可以把 FPU 理解為一個專用於浮點數運算的獨立 CPU,FPU 有自己的專用浮點數寄存器(8個),有自己的狀態寄存器,控制寄存器,這和 CPU 裡面那些寄存器都是彼此獨立的(彼此沒有影響的)。浮點數都是轉成最高精度的浮點數送到 FPU 里運算的,
運算後的結果可以再轉回普通的浮點數存儲到內存某個位置,
這裡,返回浮點數的時候從 FPU 的某個浮點數寄存器里把數據取出,並存儲到 stack 上(供 printf 訪問浮點類型的參數)。這個過程根本就繞開了 eax。所以當你把函數指針轉換了以後,根本就是影響編譯器到什麼地方去取返回值的問題,而不同的函數指針,取返回值的地點是不一樣的啊。
如果是 int,就是 eax。
浮點數,根本就不走 eax。--
此外,Calling the function specified by the converted pointer is implementation-dependent;
我認為這句話,是說:(地址是相同的,只是被指向函數的原型不同,在此條件下)取決於編譯器對源代碼編譯出來的結果。
包括:調用方是否清理棧上的參數(平衡 esp),調用方傳遞了哪些參數,以何種方式傳遞給被調用方。
這些屬於調用約定,也就是和 function type 有關。
當然,這樣做很可能會出錯,以至於運行時崩潰,後果由程序員自己負責。不論如何轉換,最後調用的代碼是不變的,區別在於轉換前後返回值的類型變了,雖然返回值的實際內容並沒有變化。
更新:雖然返回值並沒有變化,但是返回類型變化後,根據調用規則不同,調用方在不同的寄存器上獲取返回值。在cdcel中,int型的返回值存儲在eax上,float型的返回值存儲在浮點寄存器ST0上。由此導致強制轉換後返回的float值並不會影響eax寄存器的值。你的測試1不是沒有put_float也能過,而是有你也根本調用,你的兩個指針都是調用的put_int;你的測試2不是沒有put_int也能過,而是有你也根本調用,你的兩個指針都是調用的put_float。
test2 的%d的列印值與 test1 的完全沒有關係,你想多了。至於不同編譯器返回的結果不同的問題,你把@藍色和@逍遙嘆的回答綜合看一下Implementation-dependent 指的是依賴於編譯器的實現,而不是你程序的實現(好坑爹的問題)
前面有人講的 所謂的int 和 float 位元組表示不一樣!根本不是!我想問這個人是怎麼猜的啊? 不知為何這麼多人還頂它?難道這就是學術的魅力?
首先呢,函數指針是c語言層面才有的,編譯成彙編之後,只有函數地址,一個地址,僅此而已,沒有什麼參數,也沒有什麼返回值描述。一切都是棧和寄存器的操作,所以這種問題要看編譯器如何編譯~
在c/c++的編譯中,編譯器會根據不同的環境做N多不同的位元組優化!然而vs的Debug模式不僅不會優化還會添加很多冗餘位元組的調試信息。簡單分析下test1~
VS2015 test1 Release&>
//main
int(*pf)(void) = put_int;
float(*pf2)(void) = (float(*)(void))pf;
printf("%d, %f", pf(), pf2());
00A21000 call put_int (0A21030h)
00A21005 sub esp,8
00A21008 fstp qword ptr [esp]
00A2100B call put_int (0A21030h)
00A21010 push eax
00A21011 push offset string "%d, %f" (0A22108h)
00A21016 call printf (0A21050h)
00A2101B add esp,10h
return 0;
00A2101E xor eax,eax
}
00A21020 ret
//put_int
return -858993460;
00A21030 mov eax,0CCCCCCCCh
}
00A21035 ret
00A21000 call put_int (0A21030h)
00A21005 sub esp,8 //開始作用printf棧
00A21008 fstp qword ptr [esp]
然而在這三行代碼里並沒有eax的操作,而是跑去調用FPU及存取去獲取浮點數去了!!然而pf2指向的put_int並沒有操作FPU浮點數寄存器,而單純只是將 -858993460複製到eax寄存器裡面!
後面的pf()倒是非常正確和精簡~00A2100B call put_int (0A21030h)
00A21010 push eax
接下來看看gcc編譯的~
Dump of assembler code for function main:
7 {
0x0000000000400536 &<+0&>: sub rsp,0x18
8 int(*pf)(void) = put_int; 12 return 0; Dump of assembler code for function put_int: 17 return -858993460; 可以看出,代碼和vs差不多,同樣的,第一個函數pf2()雖然執行了,都是沒有對應的獲取返回值,但是gcc的第一個參數不是跑去FPU了而是跑去獲取MMX寄存器了,但是FPU和mmx貌似是同一個東西~
9 float(*pf2)(void) = (float(*)(void))pf;
10
11 printf("%d, %f", pf(), pf2());
0x000000000040053a &<+4&>: call 0x400530 &
0x0000000000400545 &<+15&>: call 0x400530 &
0x0000000000400550 &<+26&>: cvtps2pd xmm0,xmm0
0x0000000000400553 &<+29&>: mov esi,eax
0x0000000000400555 &<+31&>: mov edi,0x400610
0x000000000040055a &<+36&>: mov eax,0x1
0x000000000040055f &<+41&>: call 0x400410 &
13 }
0x0000000000400564 &<+46&>: mov eax,0x0
0x0000000000400569 &<+51&>: add rsp,0x18
0x000000000040056d &<+55&>: ret
End of assembler dump.
16 {
18
19 }
0x0000000000400530 &<+0&>: mov eax,0xcccccccc
0x0000000000400535 &<+5&>: ret
End of assembler dump.
test2也簡單分析下~,只給了gcc的~
Dump of assembler code for function main:
7 {
0x0000000000400539 &<+0&>: push rbx
8 float (*pf)(void) = put_float; 12 return 0; End of assembler dump. Dump of assembler code for function put_float: 17 return -1.0; 同樣的,我們看到第一個入棧的參數pf2(),雖然方法call了,但是處理返回結果不對!put_float明明是將數據寫入到xmm寄存器了, 但是處理返回的時候卻是跑去獲取eax,還以為是整形呢! 這種返回值類型不同的函數指針強制轉換,只會讓編譯器暈頭轉向!!根本摸不著頭腦!!
9 int (*pf2)(void) = (int (*)(void))pf;
10
11 printf("%f, %d", pf(), pf2());
0x000000000040053a &<+1&>: call 0x400530 &
0x0000000000400541 &<+8&>: call 0x400530 &
0x0000000000400549 &<+16&>: cvtps2pd xmm0,xmm0
0x000000000040054c &<+19&>: mov esi,ebx
0x000000000040054e &<+21&>: mov edi,0x400600
0x0000000000400553 &<+26&>: mov eax,0x1
0x0000000000400558 &<+31&>: call 0x400410 &
13 }
0x000000000040055d &<+36&>: mov eax,0x0
0x0000000000400562 &<+41&>: pop rbx
0x0000000000400563 &<+42&>: ret
16 {
18 }
0x0000000000400530 &<+0&>: movss xmm0,DWORD PTR [rip+0xd0] # 0x400608
0x0000000000400538 &<+8&>: ret
End of assembler dump.
0x000000000040053a &<+1&>: call 0x400530 &
float(*pf2)(void) &< = &> int(*pf)(void)
C語言中,函數指針的類型是對於編譯器層面來說的,是編譯器用來檢查語法對不對.在運行時,函數指針都是一個數字,沒什麼類型可說...
函數指針是一個值,你說它是啥它就是啥(強制轉換),按照你指定的方式運算。這個要你知道這個地址能幹啥,胡亂強制轉換程序會掛掉的。
推薦閱讀:
TAG:編程語言 | C編程語言 | MicrosoftVisualStudio | CC | C語言入門 |