使用函数指针参数求解 "passing argument of function from incompatible pointer type" 的标准方法

standard way to solve "passing argument of function from incompatible pointer type" with function pointers parameter

我有一个 list 模块实现转发列表,如下所示(最小工作示例):

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>

struct list_cell {
    void* payload;
    struct list_cell* next;
};

struct list {
    struct list_cell* head;
    int length;
};

typedef bool(*matcher_t)(const void* data);

void* findElementInList(struct list* l, matcher_t matcher) {
    struct list_cell* tmp = l->head;
    while (tmp != NULL) {
        if (matcher(tmp->payload)) {
        return tmp->payload;
        }
        tmp = tmp->next;
    }
    return NULL;
} 

struct person {
    char* name;
    char* surname;
};


static char* constName = "Thomas";
bool matchByName(const struct person* p) {
    printf("comparing %s with %s\n", p->name, constName);
    return strcmp(p->name, constName) == 0;
}

int main() {
    //initializations (made by hand to have a MWE)
    struct person globalThomas = {"Thomas", "Lot"};

    struct list* l = (struct list*) malloc(sizeof(struct list));
    l->head = (struct list_cell*) malloc(sizeof(struct list_cell));
    l->head->payload = &globalThomas;
    l->head->next = NULL;
    l->length = 1;

    void* el = findElementInList(l, matchByName);
    if (el != NULL) {
    printf("found Thomas!\n");
    } else {
    printf("Thomas not found!\n");
    }
    //deallocation
    free(l->head);
    free(l);
}

用 gcc 编译它会产生以下警告:

list.c:57:34: warning: passing argument 2 of ‘findElementInList’ from incompatible pointer type [-Wincompatible-pointer-types]
  void* el = findElementInList(l, matchByName);
                                  ^
list.c:18:7: note: expected ‘matcher_t {aka _Bool (*)(const void *)}’ but argument is of type ‘_Bool (*)(const struct person *)’
 void* findElementInList(struct list* l, matcher_t matcher) {

发生这种情况是因为 matcher_tvoid* 定义了它的参数,但我们注入了 struct person*。我想解决这个警告,但我不确定解决它的最佳方法。显然(如 in this SO answer 中所提议)我可以通过将 matchByName 签名更改为 matchByName(const void* p) 并将 void* 指针转换为 struct person* 来解决它。但我觉得这使函数成为函数目的:仅通过查看 header,我无法确定函数的输入是什么。离开 struct person* 反而更清晰。

所以我的问题是:解决此警告的最佳方法是什么?你通常会做什么来解决它?除了更改 matchByName 签名之外,还有另一种解决方法吗?.

感谢您的回复

PS:这里的objective是为了让代码更健壮,在编译时加入-Werror标志。

在 C 中,这是您能做的最好的事情:

bool matchByName(const void *px)
{
    const struct person *p = px;

    printf("comparing %s with %s\n", p->name, constName);
    return strcmp(p->name, constName) == 0;
}

您必须使函数签名符合其调用者的期望,并且其调用者是 pseudo-generic 因此它必须传递 const void * 而不是具体类型。没有办法解决这个问题。但是通过将无类型参数分配给具有正确具体类型的变量作为函数中的第一条语句,您可以让阅读代码的人清楚地了解事情,这是您能做的最好的事情。

顺便说一句,你真的应该摆脱那个 constName 全局变量,让 findElementInList 接受一个额外的 const void * 参数,它传递给不受干扰的匹配器:

bool matchByName(const void *px, const void *cx)
{
    const struct person *p = px;
    const char *nameToMatch = cx;

    printf("comparing %s with %s\n", p->name, constName);
    return strcmp(p->name, constName) == 0;
}

void *findElementInList(struct list *l, matcher_t matcher,
                        const void *matcher_args)
{
     struct list_cell *tmp = l->head;
     while (tmp) {
         if (matcher(tmp->payload, matcher_args)) {
             return tmp->payload;
         }
         tmp = tmp->next;
     }
     return 0;
 }

另请注意我对您的代码所做的文体更正:

  • 由于历史原因,在 C 中,首选样式是将函数定义的左大括号放在其自己的行上,即使所有其他左大括号都带有 "cuddled" 和它们的语句头。在某些代码中,您还会在自己的行上看到 return 类型,我认为当 return 类型上经常有很多限定符时,这是有道理的,但不要这样做,除非你我们将在整个代码库中始终如一地执行此操作。

  • 指针声明中的 * 绑定到它 右边的东西 ,而不是它左边的东西,所以它应该总是左边写有 space,右边没有 space。不这么说的人都是错的。

  • 指针与 NULL/0 的显式比较是不好的风格;只需写 if (ptr)(或者,在这种情况下,while (ptr))。我个人认为 NULL 本身就是一种糟糕的风格,你应该写成 0,但有理智的人可以不同意。

函数指针只有在它们的类型相同时才兼容。从一种类型到另一种类型的疯狂转换严格来说是未定义的行为(尽管某些情况可能在许多系统上作为 non-standard 扩展工作)。

因此您要么需要在所有情况下都使用完全相同的类型,要么编写如下的包装函数:

inline bool matchByName (const void* p)
{
  return matchByNamePerson (p);
}

也就是说,使用 void 指针的泛型编程是 old-fashioned C 并且非常危险。现在你最好写完整的 type-safe 代码来代替:

_Generic matchByName ((p), \
                      const struct person*: matchByNamePerson, \
                      const struct thing*:  matchByNameThing)(p)

可以同时保留 warnign 缺失和代码可读性的解决方案可能是引入以下宏:

#define PDOC(...) void*

//function declaration
bool matchByName(const PDOC(struct person*) p);

//function definition
bool matchByName(const PDOC(struct person*) _p) {
  const struct person* p = (const struct person*) _p;
  //insert awesome code here
}

通过这种方式,您不会生成警告,但可以保留签名的可读性(尽管这会花费您一些额外的类型)。