在Mac和Linux上关于qsort_r的声明不同

8

让我们来看一下Linux中的qsort_r函数(/usr/include/stdlib.h):

typedef int (*__compar_d_fn_t)(const void *, const void *, void *);

extern void qsort_r (void *__base, size_t __nmemb, size_t __size,
         __compar_d_fn_t __compar, void *__arg)
  __nonnull ((1, 4));

让我们来看一下 Mac (/usr/include/stdlib.h) 中的 qsort_r 函数:

void qsort_r(void *, size_t, size_t, void *, int (*)(void *, const void *, const void *));

正如您所看到的,这些声明彼此不同(参数顺序)。这很令人惊讶!是否有效地投诉某个地方来解决这个问题?


5
好的,这些函数不是标准函数... - 2501
2个回答

21

投诉某个地方解决这个问题会有效吗?

唉,不会。这种情况已经存在太久了,有太多的代码依赖它。

我认为根本问题是“为什么会出现这些不兼容性”?我来回答一下。似乎归结于BSD最先实现了它,但界面较差。ISO和后来的GNU修复了界面并决定破坏兼容性是值得的。而微软则做他们想做的。

正如@Downvoter(很好的名字)所指出的,qsort_r是一个非标准函数。如果它成为标准就好了,但你不能依赖它。在C11附录K中,qsort_s算是标准,但没有人真正实现过C11,更别说它的附录了,而 Annex K 是否是一个好主意还有待商榷

像许多C和Unix问题一样,这归结于BSD vs GNU vs Microsoft以及它们协调C扩展的无能力。Linux是GNU。OS X是许多东西的混合体,但对于C,它遵循BSD。

自2002年9月起,FreeBSD增加了qsort_r。Visual Studio 2005则提供了略有不同的qsort_s。ISO在2007年规范化了另一种略有不同的qsort_s。最后,GNU在2008年的glibc 2.8中推出了自己的版本,显然是遵循了ISO的标准。这里有一个跨越2004年至2008年的旧帖,要求在glibc中实现qsort_r,其中包含一些理由说明。

为了提醒大家,这里是C99中定义的qsort

void qsort(
    void *base, size_t nmemb, size_t size,
    int (*compar)(const void *, const void *)
);

FreeBSD在2002年9月率先采用了新的qsort_r函数。他们决定在比较函数之前添加“thunk”参数,从而打破了qsort接口。

void qsort_r(
    void *base, size_t nmemb, size_t size,
    void *thunk,
    int (*compar)(void *, const void *, const void *)
);

为什么?你需要问编写补丁的Garrett Wollman。从该补丁中可以看出,他对CMP进行的更改决定了将“thunk”放在前面是一个好的模式。也许他们认为“比较函数放在最后”是人们会记住的。不幸的是,这意味着qsortqsort_r的比较函数参数被反转了。非常令人困惑。


与此同时,始终保持创新的微软公司在Visual Studio 2005中推出了qsort_s

void qsort_s(
   void *base, size_t num, size_t width,
   int (__cdecl *compare )(void *, const void *, const void *),
   void * context
);

他们选择了最糟糕的选项,使用“s”代替其他人都在使用的“r”,可能是遵循ISO规定(见下文)或反之。他们将“thunk”放在qsort_s的末尾,保持与qsort相同的参数,但为了最大程度地混淆,“thunk”在比较函数的开头,就像BSD一样。

更糟糕的是,2007年ISO出版了TR 24731-1,向C标准库添加了边界检查(感谢@JonathanLeffler指出)。 是的,他们有自己的qsort_r,但它被称为qsort_s! 是的,它与其他所有人的不同!

errno_t qsort_s(
    void *base, rsize_t nmemb, rsize_t size,
    int (*compar)(const void *x, const void *y, void *context),
    void *context
);

他们明智地决定将 qsort_s 函数及其比较函数的参数保持为 qsort 的超集,可能是因为这样更容易记住。并且他们添加了一个返回值,这可能是个好主意。更增加混淆的是,在当时,这是一份“技术报告”,而不是 C 标准的一部分。它现在是 C11 标准的“附录 K”,仍然是可选的,但具有更大的权重。
GNU也做出了同样的决定,可能是在ISO的qsort_s之后。
void qsort_r(
    void *base, size_t nmemb, size_t size,
    int (*compar)(const void *, const void *, void *),
    void *arg
);

看着添加了 qsort_r 的 glibc 补丁,可能也更容易实现。要确定,你需要问 Ulrich Drepper。


BSD决定与qsort交换参数和比较函数可能在多年内引起了许多混乱和错误。 ISO / GNU决定保持它们相同,这可能更好。 ISO决定给它一个不同的名称。 GNU决定破坏与BSD函数的兼容性。 Microsoft决定做任何事情。现在我们面临着四个不兼容的实现。由于比较函数具有不同的签名,兼容性宏是非平凡的。
(这都是从代码重建出来的。要了解他们的实际原理,您需要查阅邮件列表档案。)
我真的不能责怪GNU或BSD或ISO或Microsoft……好吧,我可以责怪Microsoft故意试图杀死C。问题是标准化C的过程,扩展该标准以及使编译器遵循该标准的过程非常缓慢,编译器编写者有时必须做 expedient 的事情。

2
由于Linux和BSD之间的差异,POSIX不会标准化qsort_r(),因为这些差异是无法调和的。它所能做的最好的事情就是添加一个替代方案——也许是qsort_s(),但这有C11的TR 24731-1 / Annex K的错误内涵——并推广它。但是,至少在其中一个阵营中的人们将不满意,因为它需要更多的操作而不仅仅是简单的更改名称。 - Jonathan Leffler
2
@JonathanLeffler 哦不,微软的 qsort_s 甚至与 ISO 的 qsort_s 不兼容!此外,看起来 TR 24731-1 先于 glibc 的 qsort_r - Schwern
2
啊,我还没有去检查Annex K是否有qsort_s(),更不用说Microsoft与Annex K有什么不同了。POSIX可能会标准化Annex K的qsort_s(),它似乎与GNU的qsort_r()匹配,除了名称之外,这将使BSD成为受害者。或者他们可以使用posix_qsort_r()或其他名称。这基本上意味着,在(除非)POSIX做出决定并且平台实现它之前,如果您需要qsort_r()功能,则无法编写100%可移植的代码并使用系统提供的函数。 - Jonathan Leffler
1
@mtraceur,因为一个函数是针对外来范例设计的(C不是面向对象语言),所以让一个排序函数与其他所有排序函数的行为不同并不是一个好选择。我并不认为这是他们的理由。这是最糟糕的决定,因为它与现有的qsort_r不兼容,而且它的cmp函数与qsort不一致,并且他们预测了标准的错误。可预见的是,标准采用了标准C的方式:将新参数放在末尾。但那是MSVC不关心C标准的旧时代。 - Schwern
1
与问题不匹配的设计一致性价值相当低(实际上是负值,因为解决方案应该根据实际问题进行最佳设计,而表面上类似于解决相关简单问题的动机往往会妨碍这一点)。 - mtraceur
显示剩余5条评论

2
根据这里的描述,qsort是标准化的(C99),但qsort_r是GNU扩展("qsort_r()在glibc 2.8中被添加")。 因此,它不需要在各个平台上保持相同,更别说可移植性了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接