在没有辅助函数的情况下转换函数指针参数

Casting function pointer arguments without a helper function

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

有没有办法不用辅助函数就把strcmp传给qsort

我正在尝试做:

qsort(..., (int (*) (const void*, const void*) (strcmp)));

正如@some programmer dude 已经说过的,这取决于你要排序的内容。如果它是一个字符串数组,您可以使用没有辅助函数的 strcmp 并进行强制转换以避免丑陋的警告:

char s_array[100][100] = { "z", "a", ... };

qsort( s_array, 100, 100, (int (*)(const void *, const void *))strcmp );

如果它是一个指针数组,你需要一个辅助函数,因为它得到传递给指针的指针:

char *p_array[100] = { "z", "a", ... };

int cmp( const void *p1, const void *p2 )
{
    return strcmp( *(const char **)p1, *(const char **)p2 );
}

qsort( p_array, 100, sizeof *p_array, cmp );

您尝试转换只是右(右)括号放错了位置。最后的那个应该在演员表的 type 之后。所以,你可以改变:

(int (*) (const void*, const void*) (strcmp))
//                                          ^ wrong

(int (*) (const void*, const void*)) (strcmp)
//                                 ^ right

另外,虽然在 typedef 别名中隐藏指针类型是严重的 frowned-upon, 函数指针 类型是该准则的一个例外。所以,首先定义qsort比较器需要的类型是easier/clearer:

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

然后,您可以转换为该类型:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

int main(void)
{
    char list[5][8] = {
        "Fred",
        "Bob",
        "Anna",
        "Gareth",
        "Joe"
    };

    qsort(list, 5, 8, (QfnCast)(strcmp));
    for (int i = 0; i < 5; ++i) printf("%s\n", list[i]);
    return 0;
}

您尝试执行的操作存在两个问题。

首先,strcmp 的类型为 int (*)(const char *, const char *)。此类型与函数期望的类型 int (*)(const void*, const void*) 不兼容,因为参数类型不兼容。这将导致 qsort 通过不兼容的指针类型调用 strcmp,并且这样做会触发 undefined behavior.

如果 char *void * 具有相同的表示形式,则此 可能 有效,但不能保证一定会是这种情况。

第二个问题是,即使调用“有效”,最终传递给 strcmp 的实际上不是 char *,而是 char **。这意味着 strcmp 将尝试读取 char * 值,就好像它是一系列 char 值一样。

所以你必须使用辅助函数来获得你想要的结果:

int compare(const void *a, const void *b)
{
    const char **s1 = a;
    const char **s2 = b;
    return strcmp(*a, *b);
}

int (*)(const void*, const void*)int (*)(const char*, const char*) 是不兼容的函数指针类型。

不同类型之间的转换,non-compatible 函数指针类型是明确未定义的行为,C17 6.3.2.3/8 强调我的:

A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.


因此,如果您将 strcmp 转换为其他内容,您将显式调用未定义的行为。它可能会在所有指针类型大小相等的任何系统上实际工作。但如果你要依赖它,你不妨做这样的事情:

typedef union
{
  int (*strcmp) (const char*, const char*);
  int (*compare)(const void*, const void*);
} strcmp_t;

const strcmp_t hack = { strcmp };
...
qsort(str, x, y, hack.compare);

这与未定义的行为一样(并且可能在实践中起作用)但更具可读性。


你永远不能做 qsort(str, x, y, strcmp) 因为 strcmpqsort 期望的函数指针类型不兼容。函数参数传递是根据赋值完成的,因此简单赋值的规则是相关部分,来自 C17 6.5.11:

Constratints
...

  • the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;

因此 qsort(str, x, y, strcmp) 总是无效的 C,这不是实施质量问题。相反,编译器在没有诊断的情况下允许它通过将被视为无可救药地损坏。


最后如评论中所述,strcmp 仅在 bsearch/qsort 中使用才有意义,以防您拥有真正的二维字符数组,例如 char str[x][y];.根据我的经验,这是相当罕见的 use-case。在处理字符串时,您更有可能拥有 char* str[x],在这种情况下,无论如何您都必须围绕 strcmp 编写一个包装器。