这个 C 语法是什么意思?

6
这是我使用的“魔法”数组库中的内容。
void
sort(magic_list *l, int (*compare)(const void **a, const void **b))
{
    qsort(l->list, l->num_used, sizeof(void*),
         (int (*)(const void *,const void *))compare);
}

我的问题是:qsort的最后一个参数到底是做什么用的?
(int (*)(const void *, const void*))compare) 

qsort 函数接受 int (*comp_fn)(const void *,const void *) 作为其比较器参数,但此排序函数需要一个具有双指针的比较器。不知何故,上面的代码将双指针版本转换为单指针版本。可以有人帮忙解释一下吗?


4
这段 C 语法表示的是“未定义行为”(undefined behavior)。 - Joe
有人要怎么让这个东西工作?如果我看到那个“sort”函数的原型,并被要求为它写一个比较函数,我会将参数强制转换为“int **”,然后双重解引用它们以获取值,这很可能会导致程序崩溃。或者得出错误的结果。 - Praetorian
有些不对劲。比较函数可能会执行(**a > **b),但qsort只会使用元素的指针来调用compare函数。因此,它可能会多解引用一次。 或者数组中的元素是指针。而排序操作正在对指针进行排序。在这种情况下,typedef会很有用。 - alvin
6个回答

8

这正是你引用的代码所做的事情:它将指向某种类型的指针

int (*)(const void **, const void **)

转换为指向类型的指针

int (*)(const void *, const void *)

后者是 qsort 期望的内容。
类似这样的事情在质量较差的代码中经常遇到。例如,当有人想要对一个 int 数组进行排序时,他们经常会编写一个接受指向 int * 的指针的比较函数。
int compare_ints(const int *a, const int *b) {
  return (*a > *b) - (*a < *b);
}

当需要实际调用 qsort 时,它们会强制将其转换为正确的类型以抑制编译器的警告。

qsort(array, n, sizeof *array, (int (*)(const void *,const void *)) compare_ints);

这是一种“黑客”行为,会导致未定义的行为。显然这是一种不好的做法。你在例子中看到的只是这种“黑客”行为的另一种不太直接的形式。
在这种情况下,正确的做法是将比较函数声明为:
int compare_ints(const void *a, const void *b) {
  int a = *(const int *) a;
  int b = *(const int *) b;
  return (a > b) - (a < b);
}

然后可以在不进行任何强制类型转换的情况下使用它

qsort(array, n, sizeof *array, compare_ints);

一般来说,如果希望将比较函数作为比较器在 qsort (以及类似的函数)中使用,应使用 const void * 参数来实现它们。

1
@Tommy:将一个函数指针类型转换为另一个类型,然后通过转换后的值调用该函数,只有当函数类型是兼容的时才允许这样做。兼容性的完整定义在6.7.5.3/15中给出。由于const int *const void *不兼容,因此上述类型是不兼容的。 - AnT stands with Russia
在我看来,这没有任何问题。该程序想要对指针数组进行排序。强制转换可以放在回调函数内部:(void*)可以转换为任何指针,包括(void**)。这更或多或少是一个成员函数,包含std库和magic_list中的内容之间的粘合逻辑(包括转换)。 - wildplasser
在我看来,参数 (void *) 和 (void * *) 是可以相互比较的。但是对于函数指针参数而言,(void *) 和 (some_type *) 却不能比较。@Kev:不,这个讨论应该放在这里进行。 - wildplasser
@wildplasser - 不是的,现在它属于聊天,因为这就是你现在正在做的事情。聊天。 - Kev
抱歉各位,但我要介入并锁定此帖子了。你们两个都有足够的声望,在我们温馨舒适的聊天室中继续交流。谢谢。 - Kev
显示剩余23条评论

3
qsort的最后一个参数是将一个接受双指针的函数指针转换成一个接受单指针的函数指针,以便qsort可以接受它。这只是一个强制类型转换。

看起来有点奇怪,为什么不在 sort 的声明中进行强制类型转换呢? - Tom Zych
@Tom 因为 sort 声明中的指针允许您更改指针,因此您可以在转换中使用它们,这就是为什么它们是双指针的原因。 - Tony The Lion
@Tom 在 sort 函数的声明中强制转换会给函数的使用者带来关于强制转换的警告负担。Tony,说得好,我忽视了仅通过 & 解除引用的(不)方便之处。 - John
我猜测cast与sort使用qsort作为实现细节有关。如果在sort的声明中使用单指针版本,那么调用它的消费者将不得不在调用时进行转换。 - K-ballo
@Tom_Zych,因为调用者可能使用不同类型的参数(当前在sort()中使用的参数类型),如果直接传递给qsort()将会生成警告/错误。这意味着需要对每个调用者进行强制转换,这样代码就会变得丑陋。也许compare()函数是遗留代码的一部分,或者有太多的调用者。 - alvin

2
在大多数硬件上,可以假定指针在硬件级别上看起来都是相同的。例如,在具有平面64位寻址的系统中,指针始终是一个64位整数量。指针到指针或指针到指针到指针的情况也是如此。因此,用于调用带有两个指针的函数的任何方法都将与需要两个指针的任何函数一起使用。指针的具体类型并不重要。
qsort以通用方式处理指针,就好像每个指针都不透明一样。因此,它不知道或不关心它们如何被解引用。它知道它们当前的顺序,并使用比较参数来确定它们应该的顺序。
您使用的库可能会维护关于指针到指针的列表。它具有可以比较两个指向指针的指针的比较函数。因此,它将其转换为传递给qsort。这比语法更好,例如:
qsort(l->list, l->num_used, sizeof(void*), compare);

/* elsewhere */

int compare(const void *ptr1, const void *ptr2)
{
    // these are really pointers to pointers, so cast them across
    const void **real_ptr1 = (const void **)ptr1;
    const void **real_ptr2 = (const void **)ptr2;

    // do whatever with real_ptr1 and 2 here, e.g.
    return (*real_ptr2)->sort_key - (*real_ptr1)->sort_key;
}

1
在某些特定平台上,很多事情看起来在硬件层面上是相同的。然而,一般来说这并不是真的。此外,编程语言不关心在硬件层面上是否相同。语言明确指出这是一种导致未定义行为的黑客技巧。 - AnT stands with Russia
1
这不是语言规范对代码做出价值判断的位置。是的,它是!语言规范旨在将语言实现(以及这些实现的具体细节)分为两个类别:符合规范(或“好”)和不符合规范(或“坏”)。非符合规范的代码有时很有用(当您可以依赖特定实现时),但是一般来说是不好的,不应推荐。无论如何,“指针的具体类型并不重要”的想法都是错的。它显然很重要。 - Chris Lutz
不,语言规范定义了某些内容并将其他内容隐含或明确地留空。例如,Objective-C 严格来说是 C 的超集,它遵循所有 C 规则并在 C 留空的地方添加了一些新思想。但根据您的说法,这意味着每次有人编写 Objective-C 方法调用时,都是违反 C99 规范的黑客行为,而不仅仅是一个概念它没有定义。 - Tommy
所以你应该说:“语言明确说明这会导致未定义的行为”,而不是“语言明确说明这是一个hack[...]”。后者将明确排除语言的严格超集——这是一个非平凡的区别,应该由足够精确以遵守语言规范的人所欣赏。请随意发表最后的话,因为否则我只会重复自己。我认为我的答案对于问题集来说是正确的,尽管这是一个“库代码假定这种未定义行为是可靠的原因”的案例。 - Tommy
1
@Tommy:当某人依赖于C程序中特定的未定义行为时,就会发生黑客攻击。因此,这是一个两部分语句的折叠形式:1. 语言表示它是UB。2. 在代码中依赖特定形式的UB是一种hack。这样,在原始代码的上下文中,人们可以从“语言”到“hack”划清界限。这就是我所说的,不多也不少。 - AnT stands with Russia
显示剩余4条评论

1

它正在转换函数指针。我想原因是为了将比较应用于被取消引用的指针,而不是它们所指向的任何内容。


0

(int (*)(const void *,const void *))compare是一个C风格的强制类型转换,将函数指针compare转换为一个接受两个const void *参数的函数指针。


0
最后一个参数是函数指针。它指定该函数需要一个返回int类型值且接受两个const void **参数的函数指针作为输入。

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