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

qsort: Cast the comparator function itself or the parameters in the body of comparator function?

有几个明显的方法可以使用 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);

我倾向于使用前者,更多的是出于审美原因而不是其他原因。是否有任何技术原因使您更喜欢其中之一?

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

两个函数类型要兼容,return类型必须兼容,对应的参数类型也必须兼容。 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 函数所期望的兼容。

除了dbush的优秀回答外,需要注意的是int cmp(const char *s1, const char *s2)原型的替代比较函数的情况,比如strcmp 不像问题中的那样明确。 C 标准规定:

6.2.5 Types

[...] A pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.

因此指向具有原型 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 缺乏字节寻址能力)。


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

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

int cmp(const void *v1, const void *v2) {
    const int *p1 = v1, *p2 = v2;
    return (*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);
    }
}

作为附录,还有另一种调用策略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 事物的比较器。此外,它不需要任何强制转换或显式赋值,per 我对 ISO 9899 6.3.2.3,

的解释

A pointer to void may be converted to or from a pointer to any incomplete or object type . . . and back again.