qsort比较字母顺序的字符串

时间:2013-10-15 01:55:08

标签: c string sorting alphabetical qsort

我正在使用qsort()库附带的stdlib.h来排序字符串结构数组。

它本质上是一个字符串数组,但其结构包含数组。

例如:

typedef struct node {
  char name[MAX_SIZE + 1];
} Node;

然后我的包含名称的节点数组将是:

Node nodes_list[MAX_SIZE + 1];

我的问题是,我想排序nodes_list所以当我打印以下内容时:

for (i = 0; i < size; i++) {
   printf("%s\n", nodes_list[i].name);
}

它按字母顺序打印所有名称。

我想使用qsort对列表进行排序,我的比较器功能如下:

int compare(const void *a, const void *b) {
  const char **ia = (const char **)a;
  const char **ib = (const char **)b;
  return strcmp(*ia, *ib);
}

当我使用qsort运行函数时:

qsort(nodes_list, size, sizeof(Node), compare);

我遇到了分段错误(核心转储)。

我知道我在这段代码中遇到了分段错误,因为没有它,我可以打印好名单。当然没有排序。

有人可以帮忙吗?

1 个答案:

答案 0 :(得分:1)

您的数组格式的比较函数有误。

这是一个简单的清单,您可以在使用qsort时获得正确的类型和尺寸:

  1. qsort的第三个参数应为sizeof *x,其中x是第一个参数。
  2. qsort函数内部的第一件事应该是通过复制函数参数初始化的一对指针的声明。 不应该有任何演员。来自void *的演员表是没有必要的。
  3. 您可能认为自己因const而需要投射,但如果您这样做,那是因为您将const置于错误的位置。要在没有强制转换的情况下成功分配const void *,目标类型应在*关键字后只有一个constconst char *char const *都可以(相当于彼此); const char *const *也可以(并且不同); const char **错了。如果你不能在const之前加*因为你没有*,因为你输入了指针类型,这就是为什么你不应该'这样做。
  4. 除了添加const之外,在比较函数开头声明的指针类型应该与qsort的第一个参数的类型完全相同,在应用“数组衰减到指针”之后“规则,如果qsort的第一个参数是数组的名称。
  5. 在你的情况下,qsort的第一个参数是nodes_List,它是Node的数组,所以应用decay-to-pointer规则,你得到一个Node *,然后添加一个const,你得到:

    const Node *a_node = a;
    const Node *b_node = b;
    

    现在你有一对很好的正确类型的指针,你只需要以明显的方式比较它们:

    return strcmp(a_node->name, b_node->name);
    

    要解释为什么规则#4有效,你必须仔细查看内存布局。假设MAX_SIZE为15,那么MAX_SIZE + 1是一个很好的第16轮,你的Node类型包含一个16字节的char数组,而你的nodes_list包含16个,总共16 * 16 = 256字节。假设nodes_list位于内存地址0x1000。然后布局是:

    +---------------+---------------+               +---------------+
    | nodes_list[0] | nodes_list[1] |...............| nodes_list[15]|
    +---------------+---------------+               +---------------+
    ^               ^                               ^               ^
    0x1000          0x1010                          0x10f0          0x1100
    

    地址0x1000到0x10ff实际上是对象的一部分。 0x1100是后沿 - 超过结束的一个字节。

    进一步假设数组是半满的(size是8),并且填充了这8个字符串:

    Hotel Foxtrot Echo Charlie Golf Delta Bravo Alpha 
    

    并且未使用的部分用0填充。该对象由这256个字节组成(为了说明目的,我添加了空格和换行符)

    H  o  t  e  l \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    F  o  x  t  r  o  t \0 \0 \0 \0 \0 \0 \0 \0 \0
    E  c  h  o \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    C  h  a  r  l  i  e \0 \0 \0 \0 \0 \0 \0 \0 \0
    G  o  l  f \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    D  e  l  t  a \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    B  r  a  v  o \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    A  l  p  h  a \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
    ... 128 more \0's
    

    现在,您将qsort传递给此内存块的起始地址(第一个arg,nodes_list,0x1000)以及有关其内部结构的2条信息:元素数量(第二个arg,{{1} },8)和元素的数量(3rd arg,size,16)。根据该信息,它知道数组的元素位于地址0x1000,0x1010,0x1020,... 0x1070。它选择了一对 - 它选择哪一对取决于它使用的排序算法 - 简单来说,这是一个愚蠢的冒泡排序,从比较前两个元素开始。

    qsort使用元素0x1000和0x1010的地址调用比较函数。它不知道它们的类型,但它知道它们的大小。每一个都是占用16个字节的数组元素。

    您的比较功能会收到sizeof Nodea=0x1000。它们是指向16字节对象的指针 - 具体来说,它们都指向b=0x1010。如果你做错了,把它们投到struct Node,会发生什么?好吧,你得到一个char **,其值为0x1000,你必须取消引用char **才能让char **传递给char *,这样你就可以取消引用,并结束将字节strcmp加载为指针值(假设指针长度为4个字节)。在使用ASCII作为字符集的大端机器上,这是一个指向内存地址0x486f7465的指针,您将其传递给'H', 'o', 't', 'e'strcmp崩溃了。尝试strcmp的结果基本相同。

    另一件好事是,qsort如何在重新排序数组时使用成员大小信息。第三个arg不仅仅是比较所依据的对象的大小,它也是在重新排序数组时作为一个单元移动的对象的大小。在比较函数返回1(strcmp(“Hotel”,“Foxtrot”))之后,我们假设的qsort冒泡排序实现将交换0x1000和0x1010处的对象以使它们按正确的顺序排列。它将使用一系列3个memcpy,每个16字节。它必须移动所有额外的struct Node **,因为它不知道它们是无用的。那些16字节的对象对qsort是不透明的。当主数组中的对象非常大时,这可能是考虑构建辅助指针数组并对其进行qsorting而不是主数组的原因。