我目前正在阅读有关嵌入式系统的初学者书籍“制作嵌入式系统-优秀软件的设计模式”。在测试部分,他们说这是一个好主意,它具有嵌入式系统的终端接口以及一组您可以调用的命令,以测试系统中的某些内容。
他们建议实现这些命令的方式是通过包含由函数指针和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”?某些编译器会不允许我这样做吗?
答案 0 :(得分:2)
首先,您不是将函数分配给函数指针,而是将函数的指针分配给包含函数指针的对象。而且您也不需要&
,函数名称几乎在所有地方都会衰减为指向函数的指针:
const struct commandStruct commands[] = {
{"ver", (functionPointerType)CmdVersion, "Display firmware version"},
{"flashTest" (functionPointerType)CmdFlashTest, "Runs the flash unit test"}
};
这应该使警告消失,尽管某些较新版本的GCC收到了一些非常烦人的警告,这些警告会通过-Wall
启用,从而抱怨这种完全有效的构造。
请注意,当您通过此指针调用该函数时,您必须将其强制转换回原始原型,否则行为将是不确定的。
因此,如果要传递参数,则最好更改functionPointerType
,使其与CmdVersion
和CmdFlashTest
的原型都匹配;作为额外的奖励,您无需再进行那些明确的投射。
答案 1 :(得分:2)
如果两个函数类型的返回类型不同,则它们彼此不“兼容”。如果两者都用原型声明,那么如果它们采用不同数量的参数,或者如果一对对应的参数中的任何一个都不具有兼容类型,则它们也不兼容。
允许在指向不兼容函数类型的指针之间进行转换,并且某些编译器甚至可以自动执行此类转换,而不会发出警告,尽管这将构成扩展。但是,如果您通过指向与该函数的实际类型不兼容的函数类型的指针来调用函数,或者如果您传递了与该函数的声明参数类型不兼容的参数(在适用的参数提升和转换之后),则会引发未定义的行为
因此,您的书中充其量的注释充其量还是不足。
如果您想提供一个允许命令接受参数的终端接口,那么有两种不同的处理方法,但是我的第一个建议是在托管环境中模拟main()
所期望的签名。也就是说,使用以下签名声明处理程序函数:
typedef void(*functionPointerType)(int argc, char *argv[]);
可能有理由在const
上声明不同种类的argv
。这对终端接口的前端提出了相当少的解析要求,同时为您的所有处理函数提供了可以容纳参数的一致签名。
答案 2 :(得分:2)
这是一个糟糕的建议。在实践中就是这样。
定义类似于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)
这应该允许简单但通用的固件命令功能,并在错误失败时提供更多详细信息(在错误号中)。
答案 3 :(得分:1)
如果想将参数传递给函数[...],则可以从命令字符串中解析参数,然后将其传递给函数指针定义的函数
这意味着您将函数指针类型更改为带有参数的一个,不是 您将为该函数指针分配其他类型的函数指针。
例如。
typedef void (*functionType)(int argc, const char *argv[]);
起初我很困惑,因为我不认为C允许我分配一个函数,该函数将一个参数赋给期望为void的函数指针,但是我尝试了一下,然后我就可以编译并运行它了。编译器确实会给我警告。
如果您的程序未使用-Wall -Werror
进行编译(至少对于GCC样式的选项而言),则可能不正确。
使用警告进行编译只是意味着编译器将继续保持其最佳状态-C编译器通常会以您可能更了解的理由为您执行严格合法的事情。
这是完全正确的事情吗??
不。这是一种可以在某些不同情况下正常运行的hack。具体来说,如果在调用之前将函数指针转换回正确的类型(与函数原型匹配),它可能会起作用。仅当您以某种方式知道(或记录)正确的类型时,这才有意义。
答案 4 :(得分:0)
该标准不允许这样做,因为类型不兼容。
要使函数与函数指针的类型匹配,参数的数量和类型以及返回类型必须匹配。否则,将无法正确调用该函数,而您调用undefined behavior。
当您具有一组可以通过函数指针调用的函数时,所有这些函数都必须具有相同的签名。一种实现方法是,使所有此类函数接受类型为void *
的单个参数,类似于启动线程的函数。这样,您可以使用结构来包含所有有问题的参数,并将其地址传递给函数,然后函数会将void *
强制转换回期望的类型。