转换函数指针
我正在编写一个函数,它接收一个指向比较函数和MyStructs
数组的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*)
。 这编译但是未定义的行为(请参阅此SO问题 )。
选项2
创建一个全局变量int (*global_comp)(const struct MyStruct *, const struct MyStruct *)
并在myStructSort
设置global_comp=comp
。 然后创建一个函数:
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
。 这是function安全但非常糟糕的做法。
有没有神奇的完美第四选择?
编辑
我无法更改myStructSort
的API,我正在使用gcc c99 -Wall -Wextra -Wvla
编译我的代码。
选项2打破了线程安全性,因此我不会选择那个。
你指出,选项3是完全错误的。 没有理由重新实施快速排序并可能犯错误。
选项1是UB,但它适用于任何理智的编译器。 如果选择此选项,请务必添加注释。
我还会考虑:
选项4.重新设计myStructSort
的接口以获取int (*)(const void *, const void*)
或完全废弃它并直接调用qsort
。 基本上把它发回给architecht,因为他做了一个糟糕的设计选择。
以下方法仅适用于gcc
。 它是gnu扩展的一部分。 进一步请参考https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Nested-Functions.html#Nested-Functions
首先让我们确保qsort
的原型是这样的forms :
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) /* ... */ }
如果您正在使用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);
当然,这不是便携式的,它需要您定义function测试宏:
#define _GNU_SOURCE
(在FreeBSD上 – 大概是Mac OS qsort_r
有一个类似但不兼容的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); }
( 依靠ideone生活 )
正确的方法是在比较函数中从void const *
为MyStruct const *
。
这是第一个对象的明确定义,因为传递给比较函数的指针是由MyStruct const *
为void const *
,并且允许将指向void
的指针转换回原始类型(并且它是真的是唯一的事情)。
对于其他数组成员,假设将void const *
为char const *
,添加对象的偏移量,通过将对象大小乘以数组中对象的位置生成,并将其转换回void const *
将给出一个可以MyStruct const *
为MyStruct const *
的指针。
这是一个大胆的假设,但通常会有效。 可能存在不起作用的极端情况,但是通常编译器将任何struct foo
填充到其对齐的多个以确保数组成员的起始地址具有sizeof(struct foo)
的距离。
转换函数指针通常是不安全的,需要避免,因为不同的数据类型可能有不同的表示forms – 例如, void *
必须能够表达每个可能的地址,因为它可以从char *
转换,而MyStruct *
保证有一些最低有效位清零,因为任何有效对象都会对齐 – 所以这些类型的调用约定完全可能不同。
唯一合理的选择是重新编写您创建的界面,或者创建一个新界面。
在我的另一个答案中,我做了一些与冒泡排序非常类似的事情 。
简而言之,对于C,您希望sort函数具有以下forms:
void* bubbleSort(void* arr, int (*compareFcn)(void*, void*), size_t sizeOfElement, size_t numElements)
你的比较函数是这样的forms:
int compareFunction(void *a, void *b);