我有一个list
模块实现了一个foward列表,如下所示(最小工作示例):
#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_t
使用void*
定义了参数,但我们注入了struct person*
。我想解决这个警告,但我不确定解决它的最佳方法。显然(正如in this SO answer中所提出的)我可以通过将matchByName
签名更改为matchByName(const void* p)
并将void*
指针转换为struct person*
来解决此问题。但我觉得这个功能是功能目的:通过只查看标题,我无法确定函数的输入是什么。离开struct person*
会使其变得清晰。
所以我的问题是:解决此警告的最佳方法是什么?你通常做什么来解决它?除了更改matchByName
签名之外还有另一种解决方法吗?。
感谢您的回复
PS:这里的目标是在编译中添加-Werror
标志,以使代码更加健壮。
答案 0 :(得分:1)
在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;
}
您必须使函数签名与其调用者的期望相匹配,并且其调用者是伪通用的,因此它必须通过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中,首选样式是将函数定义的开括号括号放在自己的行上,即使所有其他开口花括号都是&#34; cuddled&#34;他们的发言头。在某些代码中,您还会在自己的行中看到返回类型,我认为当返回类型中通常有很多限定符时这是有意义的,但除非您要去做,否则不要这样做在整个代码库中始终。
指针声明中的*
绑定到 right 上的东西,而不是左边的东西,所以应该总是在左边写一个空格并且右边没有空间。每个人都说错了。
使用NULL / 0指针的显式比较是坏样式;只需写if (ptr)
(或者,在这种情况下,while (ptr)
)。我个人认为NULL本身是不好的风格,你应该写0,但合理的人可以不同意。
答案 1 :(得分:0)
函数指针只有在类型相同时才兼容。从一种类型到另一种类型的疯狂转换严格来说是未定义的行为(尽管某些情况可能在许多系统上作为非标准扩展)。
因此,您需要在所有情况下使用相同的类型,或者编写一个包装函数,例如:
inline bool matchByName (const void* p)
{
return matchByNamePerson (p);
}
话虽这么说,使用void指针的泛型编程是老式的C并且非常危险。现在你应该最好编写完全类型安全的代码:
_Generic matchByName ((p), \
const struct person*: matchByNamePerson, \
const struct thing*: matchByNameThing)(p)
答案 2 :(得分:0)
可能保留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
}
通过这种方式,您不会生成警告但保留签名可读性(尽管它会花费您一些额外的类型)。