C语言 - 我应该使用一个函数指针变量数组吗?

3
这个问题是关于如何在我设计程序的层面上解决我的问题。为了完成一个学校项目,我正在构建一个shell,其中包含几个内置函数。其中一个函数的目的(cmd_type)是检查提供的参数是否在这些函数列表中。以下是它的部分实现:
int cmd_type(int argc, char *argv[]) {
    if (argc == 2) {
        for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
            if (strcmp(cmds_name[i], argv[1]) == 0) {
                printf("%s is a shell builtin\n", argv[1]);
                return 0; // found it
            }
        }

        // still need to search path, call stat(path/cmd)
        errmsg("not implemented! type", 1);
    } else {
        err_msg("type", 1);
    }
}

为每个我支持的函数定义手动if语句似乎是一个糟糕的选择,因为列表可能会随着时间而扩展,并且我需要存储函数名称的列表。因此,最初,我计划定义一个函数名数组和它们指针的数组,如下所示:

char cmds_name[BUILTIN_FUNC_COUNT-1][16];
char (*cmds_ptr)(int,*char[])[BUILTIN_FUNC_COUNT-1];
// make list of built-in funcs
strcpy(cmds_name[0], "exit");
strcpy(cmds_name[1], "cd");
// make list of func pointers
cmds_ptr[0] = &cmd_exit;
cmds_ptr[1] = &cmd_cd;

它们这样使用:

// try builtin cmds
for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
    if (strcmp(cmds_name[i], argv[0]) == 0) {
        last_cmd_err = (*cmds_ptr[i])(argc, argv);
        continue; // we found it, so next loop
    }
}

然后他们会愉快地把(int argc, char *argv[])作为参数。但是cmd_path()需要访问该列表以及这些参数,所以我必须将其定义为全局变量或定义一个指向全局变量的指针...在研究过程中,我发现了这个答案,说类似的方法确实很不好:https://stackoverflow.com/a/41425477/5537652 所以我的问题是:这是解决这个问题的好方法吗?还是我应该使用if/else语句/是否有更好的方法?你会建议使用函数名称数组的全局指针吗?

1
解决这个问题的一种方法是定义一个结构体数组(而不是两个独立的名称和指针数组),并使用int builtin_cmd(int argc,char **argv,void *extra)原型定义函数。额外的指针指向函数需要的任何额外信息。最好能够设计一种类型——可能是某种结构体指针——而不是模棱两可的void *,但那是最通用的类型。不需要额外信息的函数可以传递空指针,或者可以忽略它们所传递的指针。 - Jonathan Leffler
在全局化或隐藏之前,使用名称和函数指针成对的结构创建一个结构,并通过按名称对它们进行排序的数组使用二进制搜索。 - BLUEPIXY
1
OT: 这个 char (*cmds_ptr)(int,*char[])[BUILTIN_FUNC_COUNT-1]; 和这个 for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) { if (strcmp(cmds_name[i], ... 不是很对应。 - alk
2个回答

1
我将提出以下cmd_namefunction pointer结构
typedef struct{
  char cmds_name[16];
  char (*cmds_ptr)(int,*char[]);
} cmd_type;

现在为您的所有cmds定义一个此类型的static表格:
static const cmd_type cmd_table[] = {
  {"exit", &cmd_exit},
  {"cd", &cmd_cd},
  .......
  .......
};

最后像这样访问它:

for (int i = 0; i < BUILTIN_FUNC_COUNT; i++) {
  if (strcmp(cmd_table[i].cmds_name, argv[0]) == 0) {
    last_cmd_err = (*cmd_table[i].cmds_ptr)(argc, argv);
    continue; // we found it, so next loop
  }
}

选择 if-else 和全局表之间的决定是个人口味和编码风格的问题。我更喜欢上面的解决方案,因为它提高了代码可读性并减少了混乱。您的环境中可能存在其他限制,可以影响您的决策,比如表条目数量很多并且全局内存空间有限 - 选择 if-else 路线会更好一些。
希望对你有所帮助!

1
我不会使用 if-else 语句。https://stackoverflow.com/a/41425477/5537652 提出的解决方案 (2) 没有问题。
您可以创建一个带有字符串和处理条目的函数的表格:
typedef struct cmd_desc
{
  char cmd[80];
  int builtin_cmd(int argc, char **argv, void *extra);
} CMD_DESC;

static CMD_DESC descTable[] =
{
  { "exit",                 cmd_exit      },      
  { "cd",                   cmd_cd        },   
  { "$ON_OPEN_CMD",         OnOpenCmd     },
  { "$OPEN_EXTRA_CMD",      OpenExtraCmd  },
  { "$AC",                  ActionCmd     },
  { "$AD",                  ActionDataCmd },
  { "$EC",                  ExtraCmd      },
  { "$TC",                  TextCmd       },
  { "",                     NULL          }
};

int cmd_exit (int argc, char **argv, void *extra)
{
  //...
}

访问/执行:

for (int tokenIndex=0; strcmp(descTable[tokenIndex].cmd,""); tokenIndex++) //search table 
{
    if ( strcmp( (descTable[tokenIndex]).cmd, argv[0] ) == 0 )
    { 
        int ret = (*(descTable[tokenIndex]).builtin_cmd( argc, argv, extra);
    }
}

我在我的应用程序中使用了上述方法,效果很好。
表格可以轻松扩展,而且表格的可读性比if/else链更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接