我正在编写一个函数,它接收一个指向比较函数和MyStructs
数组的指针,并且应该根据比较函数对数组进行排序:
void myStructSort(
struct MyStruct *arr,
int size,
int (*comp)(const struct MyStruct *, const struct MyStruct *)) {
qsort(arr, size, sizeof(struct MyStruct), comp);
}
不幸的是,这不会编译,因为qsort
期望比较器接收void *
个参数而不是const struct MyStruct *
。我想到了几个不好的解决方案,并想知道正确的解决方案是什么。
选项1
将comp
投放到int (*)(const void *, const void*)
。这是编译但未定义的行为(请参阅this SO question)。
选项2
创建全局变量int (*global_comp)(const struct MyStruct *, const struct MyStruct *)
并在global_comp=comp
内设置myStructSort
。然后创建一个函数:
int delegatingComp(const void *a, const void *b) {
return globalComp((const struct MyStruct *)a, (const struct MyStruct *)b);
}
并在myStructSort
致电qsort(arr, size, sizeof(struct MyStruct), delegatingComp)
。这个问题就是icky全局变量。
选项3
重新实施qsort
。这是功能上安全但非常糟糕的做法。
是否有一个神奇的完美第四选择?
修改
我无法更改myStructSort
的API,而我正在使用gcc c99 -Wall -Wextra -Wvla
编译我的代码。
答案 0 :(得分:10)
选项2打破了线程安全性,所以我不会选择那个。
当你指出时,选项3是完全错误的。没有理由重新实施快速排序并可能犯错误。
选项1是UB,但它适用于任何理智的编译器。如果您选择此选项,请务必添加评论。
我也会考虑:
选项4.重新设计myStructSort
的界面以取消int (*)(const void *, const void*)
或完全废弃它并直接致电qsort
。基本上把它发回给architecht,因为他做出了糟糕的设计选择。
答案 1 :(得分:5)
以下方法仅适用于gcc
。它是gnu扩展的一部分。进一步请参考https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Nested-Functions.html#Nested-Functions
首先让我们确保qsort
的原型位于such a form:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
然后你可以:
void myStructSort(
struct MyStruct *arr,
int size,
int (*comp)(const struct MyStruct *, const struct MyStruct *)) {
int comparator(const void * a, const void *b) {
return comp((const struct MyStruct *)a, (const struct MyStruct *)b);
}
qsort(arr, size, sizeof *arr, comparator);
}
但同样,因为它使用了gnu扩展,所以不要期望太多的可移植性。
关于你的评论:对于现代gcc
,gnu标准是默认值而不是iso标准。具体而言,最新gcc
应使用gnu11
标准。旧的正在使用gnu89
。所以,我不知道你的命令行参数,但如果没有设置-std
,这将有效。
以下是取自info gcc
的示例,以防万一链接已死。它显示了嵌套函数的类似闭包的用法:
bar (int *array, int offset, int size)
{
int access (int *array, int index)
{ return array[index + offset]; }
int i;
/* ... */
for (i = 0; i < size; i++)
/* ... */ access (array, i) /* ... */
}
答案 2 :(得分:5)
如果您正在使用gcc,那么您可以使用glibc中的qsort_r
函数,因为2.8允许您使用其他用户提供的参数指定比较器函数:
void qsort_r(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *, void *),
void *arg);
当然,这不是便携式的,它需要您定义功能测试宏:
#define _GNU_SOURCE
(在FreeBSD上 - 可能是Mac OS X - 有一个类似但不兼容的qsort_r
;区别在于用户提供的上下文参数是作为第一个提供的比较函数的参数,而不是最后一个参数。)
但如果你拥有它,它可以让你避免选项2中的全局:
/* This struct avoids the issue of casting a function pointer to
* a void*, which is not guaranteed to work. It might not be
* necessary, but I know of no guarantees.
*/
typedef struct CompContainer {
int (*comp_func)(const struct MyStruct *, const struct MyStruct *);
} CompContainer;
int delegatingComp(const void *a, const void *b, void* comp) {
return ((CompContainer*)comp)->comp_func((const struct MyStruct *)a,
(const struct MyStruct *)b);
}
void myStructSort(
struct MyStruct *arr,
int size,
int (*comp_func)(const struct MyStruct *,
const struct MyStruct *)) {
const CompContainer comp = {comp_func};
qsort_r(arr, size, sizeof(struct MyStruct), delegatingComp, &comp);
}
答案 3 :(得分:3)
正确的方法是在比较函数中从void const *
转换为MyStruct const *
。
这是第一个对象的明确定义,因为传递给比较函数的指针是由MyStruct const *
到void const *
的强制转换创建的,并且指向{{1}允许回到原来的类型(而且它真的是唯一的东西)。
对于其他数组成员,假设将void
转换为void const *
,添加对象的偏移量,通过将对象大小与数组中对象的位置相乘得到,转回char const *
会产生一个指针,可以将其强制转换为void const *
。
这是一个大胆的假设,但通常会有效。可能存在不起作用的极端情况,但通常编译器将任何MyStruct const *
填充到其对齐的多个以确保阵列成员&#39;起始地址的距离为struct foo
。
转换函数指针通常是不安全的,需要避免,因为不同的数据类型可能有不同的表示形式 - 例如,sizeof(struct foo)
必须能够表达每个可能的地址,因为它可能已经转换为void *
,而char *
保证有一些最低有效位清晰,因为任何有效对象都会对齐 - 所以这些类型的调用约定完全可能不同。
答案 4 :(得分:1)
唯一合理的选择是重新编写您创建的界面,或者重新编写新界面。
I've done something very similar with bubble sort on another answer of mine
简而言之,对于C,您希望您的排序函数具有以下形式:
void* bubbleSort(void* arr, int (*compareFcn)(void*, void*),
size_t sizeOfElement, size_t numElements)
你的比较函数的形式如下:
int compareFunction(void *a, void *b);