C qsort不能正确工作

13

我不知道哪里错了,但以下代码无法正确排序数组。

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

int compare(const void* a, const void* b)
{
    return (*(int*)a - *(int*)b);
}

int main()
{
    int x[] = { -919238029,
            -889150029,
            -826670576,
            -579609061,
            -569653113,
            -305140505,
            -216823425,
            -193439331,
            -167683147,
            -49487019,
            -45223520,
            271789961,
            275570429,
            444855014,
            559132135,
            612312607,
            664554739,
            677860351,
            1005278191,
            1031629361,
            1089012280,
            1115952521,
            1521112993,
            1530518916,
            1907515865,
            1931470931,
            -1631034645,
            -1593702794,
            -1465300620,
            -1263094822
         };
    int i;

    qsort(x, 30, sizeof(int), compare);
    for(i = 0; i < 30; i ++)
        printf("%d\n", x[i]);

    return 0;
}

会产生以下输出:

1521112993
1530518916
1907515865
1931470931
-1631034645
-1593702794
-1465300620
-1263094822
-919238029
-889150029
-826670576
-579609061
-569653113
-305140505
-216823425
-193439331
-167683147
-49487019
-45223520
271789961
275570429
444855014
559132135
612312607
664554739
677860351
1005278191
1031629361
1089012280
1115952521

我的意思是,问题一定在我的比较函数中。有人注意到任何奇怪的地方吗?

4个回答

48

很遗憾,你的“比较”溢出了。:(

原因:

当你从正数中减去一个负数时,结果不一定是正数;如果它不能用数据类型表示,它将“环绕”到另一侧。

示例:

如果你的整数只能保存从-8到7(4个位),那么当你比较4和-4时会发生什么?
好吧,你得到了8,它在二进制中是 1000,相当于-8。所以4小于-4。

道理:

不要使用减法代替比较,即使他们在学校告诉你“看这多酷”!


19

通常情况下,你不能使用减法比较整数。更确切地说,你可以这样做,但仅在你确定减法不会溢出的情况下。在你的情况下,减法会溢出,产生完全没有意义的结果(更不用提当有符号整数的减法溢出时行为是未定义的了)。

生成C风格三态比较值ab的常见习惯用法是表达式 (a > b) - (a < b)。它适用于几乎任何可比较类型的数据。在你的情况下,比较函数可能如下所示:

int compare(const void* a, const void* b)
{
  int va = *(const int*) a;
  int vb = *(const int*) b;
  return (va > vb) - (va < vb);
}

2
不过,不要在其他语言中使用布尔值尝试这个。 ;) - user541686
@Mehrdad:如果你指的是C ++,那么你是错的。这种习惯用法在C ++中也完全有效。在C ++中,类型为bool的值在减法之前会被提升为int类型。同样,在C和C++中,这种习惯用法对所有基本类型都是完全安全的。 - AnT stands with Russia
@AndreyT:我当然没有指的是C++(主要是C#和Java)。;) C++保留了许多相同的行为,因此在那里显然没问题。 - user541686
7
对于谁来说是共同的呢?我更喜欢简单明了的va < vb? -1 : va > vb? 1 : 0,它容易理解,易于反转,并且如果你有多个排序字段,很容易进行扩展。而Mehrdad的评论是准确的--有许多语言不会将布尔值隐式转换为整数(当然,这是一个C语言问题,但他笑了)。 - Jim Balter
6
我觉得 (va > vb) - (va < vb) 更加简单易懂。一开始可能看起来有些新颖,但它对称的性质使得它比使用不明显分组的 ?: 运算符更易读。 - AnT stands with Russia

1

除了Mehrad的正确答案,这里介绍一种使用SortChecker自动查找代码错误的方法:

$ LD_PRELOAD=$HOME/sortcheck-master/bin/libsortcheck.so ./a.out
a.out[38699]: qsort: comparison function is not transitive (comparison function 0x40057d (/home/iuriig/a.out+0x40057d), called from 0x400693 (/home/iuriig/a.out+0x400693), cmdline is "./a.out")
-919238029
-889150029
...

这个警告表示对于某些输入,compare 报告 x < y, y < z 而不是 x < z。为了进一步调试此问题,请使用以下命令运行:
export SORTCHECK_OPTIONS=raise=1

并检查生成的代码转储。


0

我将使用上面的信息给出一个代码示例。在我的编译器和系统中,我得到了与提问者Ram相同的结果。这表明我的整数与Ram的整数类似。我按照Mehrdad建议的方式修改了代码,使用比较运算符而不是减法。然后我成功地对数字进行了排序。

以下是代码:

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

    int compare(const void* a, const void* b)
    {
        int
            n1 = * (int *) a,
            n2 = * (int *) b;

        /*
        Usine the ternary to express along the lines of
            if
            elseif
            elseif
            .
            .
            .
            else
        */

        return 
            n1 > n2             // if
            ? 1                 // then
            : n1 == n2          // else if
            ? 0                 // then
            : -1                // else
            ;                   // end if
    }

    int main(int argc, char * argv[])
    {
        int x[] = 
        { 
            -919238029, -889150029, -826670576, -579609061, -569653113, -305140505, -216823425, -193439331,
            -167683147, -49487019,  -45223520,  271789961,  275570429,  444855014,  559132135,  612312607,
            664554739,  677860351,  1005278191, 1031629361, 1089012280, 1115952521, 1521112993, 1530518916,
            1907515865, 1931470931, -1631034645,-1593702794,-1465300620,-1263094822
        };

        int 
            i = 0,                          // index
            imax = sizeof(x)/sizeof(int);   // max value for index

        FILE * outf = 0;

        if ( !(outf = fopen("output.txt", "wt")) )
        {
            puts("outf == 0 which is an error trying to open \"output.txt\" for writing.\n");
            getch();
            return;
        }

        qsort(x, imax, sizeof(int), compare);


        for(i = 0; i < imax; i ++)
            fprintf(outf, "%d\n", x[i]);

        fclose(outf);

        return 0;
    }

然后我得到了这个输出:

-1631034645
-1593702794
-1465300620
-1263094822
-919238029
-889150029
-826670576
-579609061
-569653113
-305140505
-216823425
-193439331
-167683147
-49487019
-45223520
271789961
275570429
444855014
559132135
612312607
664554739
677860351
1005278191
1031629361
1089012280
1115952521
1521112993
1530518916
1907515865
1931470931

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接