qsort 比较函数能否始终返回非零值?

问题描述

qsort 数组上 bsearchint 的升序排序回调函数可能如下所示:

int ascending(const void *o1,const void *o2) {
    int a = *(const int *)o1;
    int b = *(const int *)o2;
    return a < b ? -1 : 1;
}

然而这个函数似乎违反了 C 标准中对 compar 函数的约束:

7.22.5.2 qsort 函数

概要

#include <stdlib.h>
void qsort(void *base,size_t nmemb,size_t size,int (*compar)(const void *,const void *));

说明
qsort 函数nmemb 对象数组进行排序,其中的初始元素由 base 指向。每个对象的大小由 size 指定。

数组的内容根据compar指向的比较函数按升序排序,调用函数时带有两个指向被比较对象的参数。如果第一个参数被认为分别小于、等于或大于第二个参数,则该函数应返回一个小于、等于或大于零的整数。

如果两个元素比较相等,则它们在结果排序数组中的顺序是未指定的。

这个比较函数是否正常或者是否会导致未定义的行为?

解决方法

C 2018 7.22.5 4 说:

当相同的对象(由 size 字节组成,不管它们在数组中的当前位置)被多次传递给比较函数时,结果应该彼此一致。也就是说,对于 qsort,它们将定义数组的总排序,对于 bsearch,相同的对象将始终以相同的方式与键进行比较。

A total order 要求 a = a。 (要从维基百科页面的定义中看出这一点:Connexity 说,对于任何 ababba。用 a 代替 b 得到 a ≤ a 或 aa。所以 aa。那么反对称的条件满足:我们有 aaaa,所以 a = a。)

,

至少将这样的函数与 bsearch 一起使用会导致未定义的行为。

这是一个演示程序。

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

int ascending(const void *o1,const void *o2) {
    int a = *(const int *)o1;
    int b = *(const int *)o2;
    return a < b ? -1 : 1;
}

int ascending1(const void *o1,const void *o2) {
    int a = *(const int *)o1;
    int b = *(const int *)o2;

    return ( b < a ) - ( a < b );
}

int main(void) 
{
    int a[] = { 2,2,1,0 };
    const size_t N = sizeof( a ) / sizeof( *a );
    
    qsort( a,N,sizeof( int ),ascending );
    
    for ( size_t i = 0; i < N; i++ )
    {
        printf( "%d ",a[i] );
    }
    putchar( '\n' );
    
    int key = 1;

    int *p = bsearch( &key,a,ascending );
    
    if ( p ) printf("*p = %d,p - a = %zu\n",*p,( size_t )( p - a ) );
    else puts( "Oops!" );

    p = bsearch( &key,ascending1 );
    
    if ( p ) printf("*p = %d,( size_t )( p - a ) );
    else puts( "Oops!" );
    
    return 0;
}

程序输出为

0 0 1 1 2 2 
Oops!
*p = 1,p - a = 3

qsort 能否工作取决于其内部实现。

但在任何情况下,您都有未定义的行为,因为比较函数不满足要求。