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

C qsort:强制转换比较器函数本身还是比较器函数体中的参数?,c,qsort,C,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, n, sizeof(double), (int (*)(cons

有两种明显的方法可以使用
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, n, sizeof(double), (int (*)(const void *, const void *))cmp);
或使用比较器:

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, n, sizeof(double), (int (*)(const void *, const void *))cmp);

我倾向于使用前者,更多的是出于美学原因。是否有任何技术上的原因使您更倾向于使用其中一种方法?

您应该避免使用后一种方法,因为它是无效的

要使两个函数类型兼容,返回类型必须兼容,相应的参数类型必须兼容。
const void*
const double*
不兼容,因此函数类型不兼容。通过不兼容的指针类型调用函数会导致错误

请注意,仅仅因为两种类型可以隐式转换,并不意味着它们是兼容的。以
const double*
const void*
为例,可以在不使用强制转换的情况下执行这两种类型之间的转换,但是这两种类型的表示形式不必相同

这意味着
const double*
传递给函数的方式可能不同于
const void*
传递给函数的方式。因此,通过调用类型为
int(*)(const-double*,const-double*)
的函数,就好像调用类型为
int(*)(const-void*,const-void*)
的函数一样,可能会以错误的方式传递参数

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

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

除了dbush极好的答案外,还应该注意,具有
int cmp(const char*s1,const char*s2)原型的备用比较函数的情况,例如strcmp
没有问题中的那么明确。C标准规定:

6.2.5类型

[…]指向
void
的指针应与指向字符类型的指针具有相同的表示和对齐要求。同样,指向兼容类型的合格或不合格版本的指针应具有相同的表示和对齐要求。指向结构类型的所有指针应具有彼此相同的表示和对齐要求。所有指向联合类型的指针应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求

因此,指向原型为
int-cmp(const-void*v1,const-void*v2)
int-cmp(const-char*v1,const-char*v2)
的函数的指针是不兼容的,但即使在
int-cmp(const-double*v1,const-double*v2)的极少数目标上,调用顺序也不大可能不同
会有问题(早期的Cray系统和CPU缺乏字节寻址能力)


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

以下是适用于所有数字类型的递增顺序实现:

int cmp(const void *v1, const void *v2) {
    const int *p1 = v1, *p2 = v2;
    return (*p1 > *p2) - (*p1 < *p2);
}
int-cmp(常数无效*v1,常数无效*v2){
常数int*p1=v1,*p2=v2;
返回(*p1>*p2)-(*p1<*p2);
}
对于浮点类型,可能需要对NaN值进行特殊处理:

// sort by increasing values, with NaN after numbers
int cmp(const void *v1, const void *v2) {
    const double *p1 = v1, *p2 = v2;
    if (isnan(*p1)) {
        return isnan(*p2) ? 0 : 1;
    } else
    if (isnan(*p2)) {
        return -1;
    } else {
        return (*p1 > *p2) - (*p1 < *p2);
    }
}
//按递增值排序,数字后加NaN
int cmp(常数无效*v1,常数无效*v2){
常数双*p1=v1,*p2=v2;
如果(isnan(*p1)){
返回值为NaN(*p2)?0:1;
}否则
如果(isnan(*p2)){
返回-1;
}否则{
返回(*p1>*p2)-(*p1<*p2);
}
}

作为附录,还有另一种调用
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;
}
#包括
#包括
静态整数双精度cmp(常数双精度*d1,常数双精度*d2)
{return(*d1>*d2)-(*d2>*d1);}
静态整数双空值cmp(常数空值*v1,常数空值*v2)
{返回双_-cmp(v1,v2);}
内部主(空){
双p[]={2.18,6.28,3.14,1.20,2.72,0.58,4.67,0.0,1,1.68};
常数大小n=sizeof p/sizeof*p;
尺寸i;
qsort(p、n、sizeof*p和double\u void\u cmp);
对于(i=0;i
尽管这有其自身的问题,但可以使用
double\u cmp
作为其他非
qsort
事物的比较器。此外,根据我对6.3.2.3的解释,它不需要任何类型转换或显式赋值

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


@jjg:代码显示的位置数并不表示它符合任何标准或规范。这是一个很好的问题,也是一个很好的答案。这一点值得深入理解,因为尽管comparator方法的cast一开始看起来很合理,但如果您考虑编译器将在
qsort
中生成(或已经生成)的代码,要真正调用comparator函数,您会看到它调用的函数有两个
void*
指针,这就是你的比较器函数。(如果所有的数据指针类型都是“相同的”——当然,它们现在在所有流行的机器上都是如此——那么错误的代码会起作用,但它仍然是错误的。)@chux,我不这么认为