qsort:转换比较器函数本身还是比较器函数体中的参数?

问题描述

有几种明显的使用 qsort方法:在比较器中强制转换:

int cmp(const void *v1,const void *v2) 
{
    const double *d1 = v1,*d2 = v2;
    ⋮
}

qsort(p,n,sizeof(double),cmp);

或转换比较器:

int cmp(const double *d1,const double *d2) 
{
    ⋮
}

qsort(p,(int (*)(const void *,const void *))cmp);

我倾向于使用前者,更多是出于审美原因。是否有任何技术上的原因让您选择一种而不是另一种?

解决方法

您应该避免后一种情况,因为它无效。

要兼容两种函数类型,返回类型必须兼容,并且对应的参数类型必须兼容。 const void *const double * 不兼容,因此函数类型不兼容。通过不兼容的指针类型调用函数会导致 undefined behavior

请注意,仅仅因为两种类型可以隐式转换并不意味着它们兼容。以const double *const void *为例,两种类型之间的转换无需强制转换,但两种类型的表示不必相同。 >

这意味着 const double * 传递给函数的方式可能与 const void * 传递给函数的方式不同。因此,通过调用 int (*)(const double*,const double*) 类型的函数,就好像它具有 int (*)(const void*,const void*) 类型一样,参数可能会以不正确的方式传递。

虽然 x64 和 ARM 系统通常会为所有指针类型使用相同的表示形式,但您可能避免使用前者,但仍然无法保证这一点。现代编译器通常会假设未定义的行为不会发生,并基于该事实执行优化。

前一种情况是正确的方法,因为函数的签名与 qsort 函数所期望的兼容。

,

作为附录,还有另一种调用 qsort 的策略:创建一个中间 qsort 必需的原型函数,该函数调用启用类型的比较函数。

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

static int double_cmp(const double *d1,const double *d2)
    { return (*d1 > *d2) - (*d2 > *d1); }

static int double_void_cmp(const void *v1,const void *v2)
    { return double_cmp(v1,v2); }

int main(void) {
    double p[] = { 2.18,6.28,3.14,1.20,2.72,0.58,4.67,0.0,1,1.68 };
    const size_t n = sizeof p / sizeof *p;
    size_t i;
    qsort(p,n,sizeof *p,&double_void_cmp);
    for(i = 0; i < n; i++) printf("%s%.2f",i ? "," : "",p[i]);
    fputs(".\n",stdout);
    return EXIT_SUCCESS;
}

虽然这有其自身的问题,但可以使用 double_cmp 作为其他非 qsort 事物的比较器。此外,它不需要任何强制转换或显式赋值,根据我对 ISO 9899 6.3.2.3 的解释

指向 void 的指针可以转换为或从指向任何 不完整或对象类型。 . .然后再回来。

,

除了dbush优秀的回答,需要注意的是,替代比较函数的原型为iss,比如int cmp(const char *s1,const char *s2)的情况不是很清楚切为问题中的那个。 C 标准规定:

6.2.5 类型

[...] 指向 strcmp 的指针应与指向字符类型的指针具有相同的表示和对齐要求。类似地,指向兼容类型的限定或非限定版本的指针应具有相同的表示和对齐要求。所有指向结构类型的指针都应具有相同的表示和对齐要求。所有指向联合类型的指针都应具有相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。

因此,指向具有原型 voidint cmp(const void *v1,const void *v2) 的函数的指针不兼容,但即使在 {{1 }} 会有问题(早期的 Cray 系统和 CPU 缺乏字节寻址能力)。


您没有提供比较函数的代码:简单地返回值的差异 (int cmp(const char *v1,const char *v2)) 是一个常见的错误。这不适用于浮点值,也不适用于 int cmp(const double *v1,const double *v2) 值,因为减法可能会溢出。

这是一个适用于所有数字类型的递增顺序实现:

*d1 - *d2

对于浮点类型,可能需要对 NaN 值进行特殊处理:

int