[英]In C, should I define (not declare/prototype) a function that takes no arguments with void or with an empty list?
[英]Should I assign a function that takes arguments to function pointers that take a void?
我目前正在閱讀有關嵌入式系統的初學者書籍“制作嵌入式系統-優秀軟件的設計模式”。 在測試部分,他們說這是一個好主意,它具有嵌入式系統的終端接口以及一組您可以調用的命令,以測試系統中的某些內容。
他們建議實現這些命令的方式是通過包含由函數指針和const char *組成的c結構數組。 然后使用您的命令名稱和要由該命令調用的各個函數來初始化命令數組。
您讓用戶選擇一個字符串,然后將該字符串與命令數組中的char *進行比較,如果它與特定條目匹配,則調用結構中的相應函數。
這是示例代碼。
typedef void(*functionPointerType)(void);
struct commandStruct
{
char const *name;
functionPointerType execute;
char const *help
};
const struct commandStruct commands[] = {
{"ver", &CmdVersion, "Display firmware version"},
{"flashTest" &CmdFlashTest, "Runs the flash unit test"}
};
我知道這很好。 我不明白的是,隨后的扔掉評論說,如果有人想將參數傳遞給具有一個參數的函數,則會從命令字符串中解析參數並將它們傳遞給函數指針定義的函數。
一開始我很困惑,因為我不認為C允許我分配一個函數,該函數需要一個參數作為參數,而該函數指針期望為void,但是我嘗試了一下,然后我就可以編譯並運行它了。 編譯器確實給了我警告。
我想我的問題是:這是完全正確的做法還是有點“ hack”? 某些編譯器會不允許我這樣做嗎?
好吧,首先,您不是將一個函數分配給一個函數指針,而是將一個指向函數的指針分配給了一個持有指向函數的指針的對象。 而且您也不需要&
,函數名稱幾乎在所有地方都會衰減為指向函數的指針:
const struct commandStruct commands[] = {
{"ver", (functionPointerType)CmdVersion, "Display firmware version"},
{"flashTest" (functionPointerType)CmdFlashTest, "Runs the flash unit test"}
};
盡管某些較新版本的GCC已經獲得了一些非常煩人的警告,這些警告將通過-Wall
啟用,但它們會抱怨這種完全有效的構造,但這應該會使警告-Wall
。
請注意,通過此指針調用該函數時, 必須將其強制轉換回原始原型,否則該行為將是不確定的。
因此,如果要傳遞參數,則最好更改functionPointerType
,使其與CmdVersion
和CmdFlashTest
的原型都匹配; 作為額外的獎勵,您無需再進行那些明確的強制轉換。
如果兩個函數類型的返回類型不同,則它們彼此不“兼容”。 如果兩者都用原型聲明,那么如果它們采用不同數量的參數,或者如果一對對應的參數中的任何對都沒有兼容類型,那么它們也不兼容。
允許在指向不兼容函數類型的指針之間進行轉換 ,並且某些編譯器甚至可以自動執行此類轉換,而不會發出警告,盡管這將構成擴展。 但是,如果您通過指向與該函數的實際類型不兼容的函數類型的指針來調用函數,或者如果您傳遞了與該函數的聲明參數類型不兼容的參數(在適用的參數提升和轉換之后),則會引發未定義的行為。
因此,您的書的最好的評論充其量是不足的。
如果要提供一個允許命令接受參數的終端接口,那么有幾種不同的方法可以使用它,但是我的第一個建議是在托管環境中模擬main()
的簽名。 也就是說,使用以下簽名聲明處理程序函數:
typedef void(*functionPointerType)(int argc, char *argv[]);
可能有理由在argv
上聲明各種const
。 這對終端接口的前端提出了相當低的解析要求,同時為您的所有處理程序函數提供了可以容納參數的一致簽名。
這是不好的建議。 這是在實踐中的完成方式。
定義類似於main()
回調函數,即以字符串數和字符串作為參數: int callback(int argc, char *argv[])
。
因此,您的命令列表可能是例如
static const struct {
const char *name;
const char *help;
int (*func)(int argc, char *argv[]);
} firmware_cmd[] = {
{ "help", "help [ command ]", help_func },
{ "ver", "ver", display_version },
{ "flashtest", "flashtest", flash_text },
{ 0 }
};
在固件命令解析器/詞法分析器中,定義一些特定的回調函數返回值:
enum {
FIRMWARE_CMD_OK = 0,
FIRMWARE_CMD_ARGS, /* Invalid arguments! */
FIRMWARE_CMD_HELP, /* Command help asked */
/* All others are error/failure codes */
};
現在,當命令解析器/詞法分析器調用該函數時,它會根據返回值輸出其他文本:
FIRMWARE_CMD_OK
:“確定”
FIRMWARE_CMD_ARGS
:”無效的參數。運行'help COMMAND'以查看幫助,或運行'help'以查看完整的命令列表。
FIRMWARE_CMD_HELP
: ->help
文本。
所有其他返回值: Error (returnvalue)
返回值Error (returnvalue)
如果錯誤失敗,這將允許簡單但通用的固件命令功能,並提供其他詳細信息(在錯誤號中)。
如果想要將參數傳遞給函數,則可以從命令字符串中解析參數,並將其傳遞給函數指針定義的函數
這意味着您將函數指針類型更改為 帶有參數的一個 , 而不是 將不同類型的函數分配給該指針。
例如。
typedef void (*functionType)(int argc, const char *argv[]);
一開始我很困惑,因為我不認為C允許我分配一個函數,該函數需要一個參數作為參數,而該函數指針期望為void,但是我嘗試了一下,然后我就可以編譯並運行它了。 編譯器確實給了我警告。
如果您的程序未使用-Wall -Werror
編譯(至少對於GCC樣式的選項),則可能不正確。
使用警告進行編譯只是意味着編譯器將繼續保持其最佳狀態-C編譯器通常以您可能更了解為由,允許您執行嚴格不合法的事情。
這是完全正確的事情嗎?
不可以。這是一種駭客程序,可以在某些(不同)情況下正常運作。 具體來說,如果在調用之前將函數指針轉換回正確的類型(與函數原型匹配),它可能會起作用。 僅當您以某種方式知道(或記錄)正確的類型時,這才有意義。
該標准不允許這樣做,因為類型不兼容。
為了使函數與函數指針的類型匹配,參數的數量和類型以及返回類型必須匹配。 否則,該函數將無法正確調用,並且您將調用undefined behavior 。
當您具有可以通過函數指針調用的一組函數時,所有這些函數都需要具有相同的簽名。 一種實現方法是讓所有此類函數接受類型為void *
的單個參數,類似於啟動線程的函數。 這樣,您可以使用結構包含所有有問題的參數,並將其地址傳遞給函數,然后該函數會將void *
強制轉換回期望的類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.