C “解决问题的标准方法”;从不兼容的指针类型传递函数的参数;使用函数指针参数

C “解决问题的标准方法”;从不兼容的指针类型传递函数的参数;使用函数指针参数,c,compiler-warnings,c99,C,Compiler Warnings,C99,我有一个list模块,实现了一个前向列表,如下所示(最简单的工作示例): 这是因为matcher\u t用void*定义了它的参数,但是我们注入了struct person*。我想解决这个警告,但我不确定最好的解决方法。显然(如中所述),我可以通过将matchByName签名更改为matchByName(const void*p)并将void*指针转换为struct person*来解决这个问题。但我觉得这样做可以使函数成为函数的目的:通过只查看标题,我无法确定函数的输入是什么。离开struct

我有一个
list
模块,实现了一个前向列表,如下所示(最简单的工作示例):

这是因为
matcher\u t
void*
定义了它的参数,但是我们注入了
struct person*
。我想解决这个警告,但我不确定最好的解决方法。显然(如中所述),我可以通过将
matchByName
签名更改为
matchByName(const void*p)
并将
void*
指针转换为
struct person*
来解决这个问题。但我觉得这样做可以使函数成为函数的目的:通过只查看标题,我无法确定函数的输入是什么。离开
struct person*
会让事情变得更清楚

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

谢谢你的回复


PS:这里的目标是在编译中添加
-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;
}
您必须使函数签名与其调用方的期望相匹配,并且它的调用方是伪泛型的,因此它必须传递
const void*
,而不是具体的类型。这是没有办法的。但是,通过将非类型化参数指定给一个具有正确具体类型的变量作为函数中的第一条语句,您可以让阅读代码的人明白这一点,这是您所能做的最好的事情

顺便说一句,您确实应该通过让
findElementInList
接受一个额外的
const void*
参数,将该
constName
全局变量清除掉:

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语言中,首选的样式是将函数定义的开头大括号放在自己的行中,即使所有其他的开头大括号都与它们的语句头“拥抱”在一起。在一些代码中,您还会看到返回类型本身的行,我认为当返回类型上经常有很多限定符时,这是有意义的,但是不要这样做,除非您要在整个代码库中一致地这样做

  • 指针声明中的
    *
    绑定到右边的对象,而不是左边的对象,因此它应该总是在左边有空格,右边没有空格。每一个不这样说的人都是错的

  • 指针与NULL/0的显式比较是错误的样式;只要写
    if(ptr)
    (或者,在本例中,
    while(ptr)
    )。我个人认为NULL本身就是一种糟糕的风格,你应该改为写0,但理性的人可能不同意这一点


函数指针只有在类型相同时才兼容。严格来说,从一种类型到另一种类型的广泛转换是未定义的行为(尽管某些情况下可能在许多系统上作为非标准扩展)

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

inline bool matchByName (const void* p)
{
  return matchByNamePerson (p);
}
也就是说,使用void指针的泛型编程是老式的C语言,非常危险。现在,您最好编写完全类型安全的代码:

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

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

#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
}

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

感谢您的快速回答!我认为这是解决警告的唯一方法,但我需要确认我的假设。至于风格上的修正,我仍然需要理解为什么
void*foo()
优于
void*foo()
:我的意思是,唯一的缺点是在同一行声明了几个变量,这是糟糕的代码风格。这也会造成对解引用操作的混淆。但是我想我需要发布一个关于它的问题:)@Koldar关于这个问题,可能还有其他几个问题,一个新的问题可能会以“基于意见”的方式结束。您是对的,如果您在同一声明中有多个变量(不一定在同一行上),那么左搂
*
只会有危险(从某种意义上说,它可能会使程序出现错误),但它会给人类读者造成混乱,大多数情况下,可读性是最重要的属性代码。我需要考虑一下。。。无论如何,谢谢你的自省。总是喜欢在这样的争论问题上了解别人我不得不投反对票,因为100%的主观编码风格的论点。我可以反驳所有这些观点,但这不是一个合适的地方。没有理由将主观的、个人的编码风格引入到一个很好的答案中——如果你改变了发布代码的风格,这很好,但是没有必要因为为什么这种风格被认为是优越的争论而脱轨。C11中没有引入这种语法吗?我在C99,对吗now@Koldar
inline
在C99中引入,而
\u Generic
在C11中引入。这是一个已有7年历史的标准,并不是什么新鲜事。我不得不否决这一点,因为我在一个问题中建议
\u Generic
,而这个问题既没有提到C11也没有提到
\u Generic
,也没有主观地将C的唯一开放泛型编程机制描述为“老式”(我会给你“相当危险”)
\u Generic
只能在您可以枚举将与之一起使用的所有具体类型时使用,因此它不能替代OP使用的技术。@zwol在使用C标记时,假定使用了标准C。根据
_Generic matchByName ((p), \
                      const struct person*: matchByNamePerson, \
                      const struct thing*:  matchByNameThing)(p)
#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
}