[英]How to explain calling a C function from an array of function pointers?
此示例https://godbolt.org/z/EKvvEKa6T使用此語法調用 MyFun()
(*((int(**)(void))CallMyFun))();
是否有該混淆語法的 C 細分來解釋它是如何工作的?
#include <stdio.h>
int MyFun(void)
{
printf("Hello, World!");
return 0;
}
void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };
int main(void)
{
size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));
return (*((int(**)(void))CallMyFun))();
}
void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };
的行為未由 C 標准定義,因為它將指向 function ( MyFun
) 的指針轉換為指向 object 類型 ( void *
) 的指針。 C 2018 6.3.2.3中規定了指針的轉換,除null指針外,均未涵蓋指向object類型的指針與指向function類型的指針之間的轉換。
代碼size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));
將CallMyFun
設置為funarray
的元素 2 的地址,前提是指向 integer size_t
類型的指針轉換“自然”地工作,這不是 C 標准所要求的,但有意為之。
在(*((int(**)(void))CallMyFun))();
,我們有(int(**)(void)) CallMyFun
。 這表示將CallMyFun
轉換為指向 function 的指針,不接受 arguments 並返回int
。 在C中使用異構function指針表時,需要進行類型轉換,因為C沒有提供通用的function指針機制,所以不能怪作者。 但是,這不僅會轉換為指向 function 類型的指針,還會轉換為指向 function 類型的指針,這是一個錯誤。
該數組包含指向void
的指針,而不是指向函數指針的指針。 C 標准不要求它們具有相同的大小或表示形式。
然后代碼應用*
,打算檢索指針。 這是另一個錯誤。 如果指向void
的指針具有相同的大小並且表示具有指向 function 的指針的指針,則訪問void *
(這是存儲在數組中的內容)作為指向 function 的指針(這是使用它檢索它的內容)表達式確實)違反了 C 2018 6.5 7 中的別名規則。
但是,如果 C 實現確實支持這個以及所有先前的轉換和問題,則結果是指向 function MyFun
的指針,然后應用()
調用 function。
假設數組必須支持異構 function 類型,編寫代碼的正確方法可能是:
// Use array of pointers to functions (of a forced type).
void (*funarray[])(void) = { NULL, NULL, (void (*)(void)) MyFun, NULL, NULL };
…
// Get array element using ordinary subscript notation.
void (*CallMyFunc)(void) = funarray[2];
// Convert pointer to function’s actual type, then call it.
return ((int (*)(void)) CallMyFunc)();
如果數組可以齊次,那么代碼應該這樣寫:
int (*funarray[])(void) = { NULL, NULL, MyFun, NULL, NULL };
…
return funarray[2]();
要了解這里發生了什么,我們應該逐段查看代碼的每個部分(至少是奇數段):
void *funarray[] = { NULL,NULL,&MyFun,NULL,NULL };
在上面,我們正在創建一個初始化為最“空”的指針數組(即NULL
),但在索引 2 處我們有一個指向MyFunc
的指針。 跟蹤我們 go 的類型很有幫助,所以此時,我們有類型為int (*)(void)
的&MyFunc
(C 中的函數指針語法有點奇怪,因為*
和可能的標識符位於中間而不是位於最后),它立即變成數組中的void *
。 為什么數組的類型是void *
當它看起來包含 function 指針時有點奇怪,但讓我們假設有一個很好的理由......
size_t CallMyFun = (size_t)&funarray + (2 * sizeof(funarray[0]));
這段代碼是一種相當復雜的訪問數組和獲取第二個元素的方法,方法是將數組的頭部轉換為size_t
,然后添加正確的字節數以獲得第二個條目的地址(更簡單的方法是(size_t) &funarray[2]
)。 在這里,我們再次丟失了類型信息,從最初的int (*[])(void)
到void*[]
到size_t
。
return (*((int(**)(void))CallMyFun))();
現在我們知道CallMyFunc
包含一個指向 function 指針的指針,即數組中MyFunc
指針的地址,如果我們想調用它,我們需要將它轉換回正確的類型並正確地解引用它。 因此,展開調用,我們直接有((int(**)(void))CallMyFun)
將類型不正確的CallMyFunc
從size_t
轉換為雙精度 function 指針,即指向 function 指針的指針,其語法為int (**)(void)
,就像任何其他需要兩個 * 來表示的雙指針一樣。 接下來,由於還不是 function 指針,我們需要取消引用它以獲得類型為int (*)(void)
的指針,因此(*((int(**)(void))CallMyFun))
。 最后,我們要實際調用 function 指針,所以最后一組父對象。
正如評論中所提到的,這段代碼通過改變類型和使用不尋常的方式訪問 arrays 而變得相當混亂; 通常最好保持類型一致,因為它可以讓編譯器幫助您避免錯誤,並使代碼對您自己和其他人更具可讀性。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.