带括号的三元表达式在 C 中的函数指针声明中返回函数名称的基本原理

Rationale for a parenthesized ternary expression returning a function name in a function pointer declaration in C

在 K&R2 书中,在函数指针部分的第 119 页,函数指针的参数声明如下:

(int (*)(void*,void*))(numeric ? numcmp : strcmp)

numcmpstrcmp是函数名,numeric是一个布尔变量,决定表达式声明的函数指针指向这两个函数中的哪一个。

我不明白这是如何工作的,为什么会这样。如果我试图写这个表达式,我的尝试会更像:

int (*(numeric ? numcmp : strcmp))(void*,void*)

我能理解 K&R 结构的最好方法是第一个括号部分 - (int (*)(void*,void*)) - 作为一个函数,第二个 - (numeric ? numcmp : strcmp) - 作为函数参数,和整个 returns 函数指针声明。但是这样想与我对 C 的任何了解都没有关系。

我已经阅读了一些关于如何理解 C 中的复杂指针表达式的优秀指南。您基本上 "spiral out" 从最内层的表达式开始。但是这个让我难住了,它不符合要求。有人可以解释一下吗?

(int (*)(void*,void*)) 是正常的 type-cast.

如果我们创建类型别名

typedef (int (*function_type)(void*,void*));

使用起来可能更容易理解:

(function_type) (numeric ? numcmp : strcmp)

简而言之,三元表达式returns一个指向函数的指针,然后将结果(函数指针)转换为特定类型。

表达式来自 Brian W Kernighan 和 Dennis M Ritchie 的 p119 The C Programming Language, 2nd Edn (1988).

它只是将两个函数指针之一(由三元表达式选择)转换为通用类型,int (*)(void *, void *) 以匹配 qsort() 函数变体的签名,即写在 K&R2 的 p120。

但是,IMO,根据 C 标准,那段代码正式 运行 不符合 'undefined behaviour'。

C11 [§6.3 Conversions]

§6.3.2.3 Pointers ¶8

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

您可以在§6.2.7 Compatible type and composite type and §6.7.6.3 Function declarators (including prototypes) ¶15中查看兼容类型的要求。

问题中提到的代码是对标准 C qsort() 函数变体的调用。标准函数具有签名:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

在书中的代码中,他们使用了自己的相关函数 qsort(),签名却大不相同:

void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *));

现在,qsort() 变体中的代码将使用两个 void * 值调用由 comp 标识的函数。因此,为了避免未定义的行为,作为比较器 传递给 qsort() 的函数应该 具有签名:

int comparator(void *p1, void *p2);

现在,代码通过比较器使用:

(int (*)(void *, void *))(numeric ? numcmp : strcmp)

p106 上的 strcmp 函数实现与标准 C int strcmp(const char *, const char *) 不太匹配;它是他们自己的次要变体,缺少 const 限定符 (int strcmp(char *, char *))。但是,p119 上的代码包含 <string.h>,因此它可能是使用的标准版本。 thenumcmp` 函数的签名如下:

int numcmp(char *, char *);

调用中的转换是合法的 — 您可以将函数指针从一种类型转换为另一种类型(然后再转换回来)。在最严格的解释中,不合法的是它们的 qsort() 变体将调用这些函数,就好像它们的类型是 int function(void *, void *) 并且标准说 "that's undefined behaviour".

此外,§6.5.15 Conditional operator表示:两边的两个表达式必须满足一系列6个条件中的一个,其中相关的一个是:

  • both operands are pointers to qualified or unqualified versions of compatible types;

现在,假设这两个函数都具有签名 int function(char *, char *),就可以了。如果 strcmp() 是标准的 C 版本,由于 const-限定符,它如履薄冰。

鉴于它是自定义的qsort()并且两个比较器具有相同的签名,因此使用此签名是合理的:

void qsort(void *lineptr[], int left, int right, int (*comp)(char *, char *));

那么在调用 qsort() 时就没有必要强制类型 — 函数指针参数将很简单:

(numeric ? numcmp : strcmp)

并且 qsort() 中的代码不需要更改,因为 C 中有从 void * 到任何其他类型的自动转换 — 在本例中为 char *

总结

在实践中,您几乎总是会避开 K&R2 中显示的代码。但严格来说,代码正在调用未定义的行为,因为它没有将函数指针强制转换回它们的原始类型。

如果您使用的是标准 C qsort(),您应该始终传递与签名匹配的比较器:

int comparator(const void *p1, const void *p2);

因此,您不需要在调用 qsort() 时对函数指针进行强制转换,因为 qsort() 将使用该签名来调用您的函数。在您的比较器函数中,代码会将两个 const void * 值转换为正确类型的合适 (const) 指针,并 运行 使用这些类型进行比较。