来自 K&R 的那段特定代码(Brian W Kernighan 和 Dennis M Ritchie —
The C Programming Language, 2nd Edn 1988) 不符合标准 C 并且不是您现在编写代码的方式。
这是一个严重的指控 - 但 C11 标准说 (§6.3 Conversions,特别是在 §6.3.2.3 Pointers ¶8:
指向一种类型的函数的指针可以转换为指向另一种类型的函数的指针,然后再返回;结果应与原始指针比较。如果转换后的指针用于调用类型与引用类型不兼容的函数,则行为未定义。
在书中,qsort() 函数有签名:
void qsort(void *v[], int left, int right, int (*comp)(void *, void *));
这与标准 C qsort() 的接口不同,而且是如履薄冰——您应该避免重新定义标准 C 函数,尤其是在重新定义具有不同接口的情况下。理想情况下,函数会被重命名,可能是quicksort()。
因此,作为comp 传递的函数指针应该与签名匹配
int (*comp)(void *, void *);
但是这两个函数都有签名:
int numcmp(char *s1, char *s2); /* Treating strings as numbers */
int strcmp(char *s1, char *s2);
qsort() 中的代码将函数指针视为int (*comp)(void *, void *),因此根据 §6.3.2.3 ¶8,代码具有未定义的行为。转换为公共类型很丑陋,但必须满足调用 qsort() 的要求,并且可以通过在被调用函数 (qsort()) 中转换回原始类型来撤消,但它不会转换函数指针。
为了满足现代 C 的要求,numcmp() 函数需要重写:
int numcmp(void *v1, void *v2)
{
double v1 = atof((char *)v1);
double v2 = atof((char *)v2);
if (v1 < v2)
return -1;
else if (v1 > v2)
return +1;
else
return 0;
}
修改后的strcmp() 将被类似地重写——并重命名以避免与strcmp() 的标准 C 实现冲突。对qsort() 的调用则不需要对比较器参数进行强制转换。仍然需要对数组指针参数进行强制转换,因为 char ** 不会自动转换为 void **(但会转换为 void *)。
现在,这是由 C 标准支持的理论观点。但是,在实践中,您通常会逃脱此类滥用(但编译器可能会生成有关滥用的警告)。
在标准 C 中,qsort() 函数具有原型:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
这在几个方面有所不同,特别是比较器传递了const指针,而数组是void *而不是书中的void **。
上面的numcmp() 函数必须重写。如果您正在对字符串数组 (char **) 进行排序,这正是本书所做的,那么参数的类型为 char ** 转换为 void *。
这是一些对行数组进行排序的代码,使用 POSIX getline() 来读取行。此代码可在 GitHub 上的 SOQ(堆栈溢出问题)存储库中以文件 sortlines2.c 的形式在 src/sortfile 子目录中找到;还有一个sortlines3.c,它的内存效率更高。这段代码让getline() 为每一行分配新内存,这很愉快,但它分配的最小大小通常远大于您正在阅读的行。
/*
** Originally a demonstration that a comparator in an SO answer was incorrect.
** Now a simple demonstration of reading a file and sorting.
*/
#include "posixver.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Correct comparator */
static int compare(const void *p_lhs, const void *p_rhs)
{
const char *lhs = *(char **)p_lhs;
const char *rhs = *(char **)p_rhs;
return strcmp(lhs, rhs);
}
/* Lines in array are terminated by a newline */
static void dump_array(const char *tag, size_t size, char *array[size])
{
printf("%s:\n", tag);
for (size_t i = 0; i < size; i++)
printf("%.2zu: %s", i, array[i]);
}
int main(void)
{
char **ptrs = 0;
size_t numptrs = 0;
char *buffer = 0;
size_t buflen = 0;
size_t count = 0;
while (getline(&buffer, &buflen, stdin) != -1)
{
if (count == numptrs)
{
size_t newnum = (numptrs + 2) * 2;
void *newptrs = realloc(ptrs, newnum * sizeof(*ptrs));
if (newptrs == 0)
{
fprintf(stderr, "Out of memory (%zu bytes requested)\n", newnum * sizeof(*ptrs));
exit(1);
}
ptrs = newptrs;
numptrs = newnum;
}
ptrs[count++] = buffer;
buffer = 0;
buflen = 0;
}
free(buffer);
dump_array("Before sorting", count, ptrs);
qsort(ptrs, count, sizeof(char *), compare);
dump_array("After sort", count, ptrs);
for (size_t i = 0; i < count; i++)
free(ptrs[i]);
free(ptrs);
/* Avoid an 'in use at exit (reachable)' record in valgrind */
/* Mac OS X El Capitan 10.11.4, Valgrind 3.12.0.SVN */
/* macOS High Sierra 10.13.4, Valgrind 3.14.0.GIT */
fclose(stdin);
return 0;
}
请注意,qsort() 的参数没有强制转换——它们应该是不必要的。
要编写数字比较器,您需要:
int numcmp(const void *p1, const void *p2)
{
double v1 = atof(*(char **)p1);
double v2 = atof(*(char **)p2);
if (v1 < v2)
return -1;
else if (v1 > v2)
return +1;
else
return 0;
}
然后简单地将numcmp(不带演员表)传递给qsort()。