我正在使用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);
我遇到了分段错误(核心转储)。
我知道我在这段代码中遇到了分段错误,因为没有它,我可以打印好名单。当然没有排序。
有人可以帮忙吗?
答案 0 :(得分:1)
您的数组格式的比较函数有误。
这是一个简单的清单,您可以在使用qsort时获得正确的类型和尺寸:
sizeof *x
,其中x
是第一个参数。void *
的演员表是没有必要的。const
而需要投射,但如果您这样做,那是因为您将const
置于错误的位置。要在没有强制转换的情况下成功分配const void *
,目标类型应在*
关键字后只有一个const
。 const char *
和char const *
都可以(相当于彼此); const char *const *
也可以(并且不同); const char **
错了。如果你不能在const
之前加*
因为你没有*
,因为你输入了指针类型,这就是为什么你不应该'这样做。 const
之外,在比较函数开头声明的指针类型应该与qsort的第一个参数的类型完全相同,在应用“数组衰减到指针”之后“规则,如果qsort的第一个参数是数组的名称。在你的情况下,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 Node
和a=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而不是主数组的原因。