带括号的三元表达式在 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)
numcmp
和strcmp
是函数名,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]
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>
,因此它可能是使用的标准版本。 the
numcmp` 函数的签名如下:
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
) 指针,并 运行 使用这些类型进行比较。
在 K&R2 书中,在函数指针部分的第 119 页,函数指针的参数声明如下:
(int (*)(void*,void*))(numeric ? numcmp : strcmp)
numcmp
和strcmp
是函数名,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]
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>
,因此它可能是使用的标准版本。 the
numcmp` 函数的签名如下:
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
) 指针,并 运行 使用这些类型进行比较。