为什么可以使用具有错误签名的比较函数调用“qsort”并且编译没有警告

Yeo*_*eng 2 c void-pointers qsort

我正在努力整合代码库(将qsort compar函数移动到新的标头/库,以便可以在不复制/意大利面的情况下共享它),并注意到在此过程中出现了一些奇怪的情况。

这是一个示范性清单:

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

 /** One record has three fields.
 * Each field contains a NULL terminated string of length at most 7 characters. */
typedef char Record[3][8];

int main(void)
{
    Record database[5] = {0};

    strcpy(database[0][0], "ZING");
    strcpy(database[0][1], "BOP");
    strcpy(database[0][2], "POW");

    strcpy(database[1][0], "FIDDLY");
    strcpy(database[1][1], "ECHO");
    strcpy(database[1][2], "ZOOOM");

    strcpy(database[2][0], "AH");
    strcpy(database[2][1], "AAAAA");
    strcpy(database[2][2], "AH");

    strcpy(database[3][0], "BO");
    strcpy(database[3][1], "DELTA");
    strcpy(database[3][2], "FO");

    strcpy(database[4][0], "FRRING");
    strcpy(database[4][1], "CRASH");
    strcpy(database[4][2], "FOO");

    //(gdb) ptype record_compare_field_1
    //type = int (char (*)[8], char (*)[8])
    int record_compare_field_1();
    qsort(database, 5, sizeof(Record), record_compare_field_1);
    for (int i = 0; i < 5; i++){
        printf("%s\t%s\t%s\n", database[i][0], database[i][1], database[i][2]);
    }
}

/* Compares Records at field one. */
int record_compare_field_1(Record rowA, Record rowB)
{
    return strcmp(rowA[1], rowB[1]);
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

$ gcc -Wall main.c
$ ./a.out
AH      AAAAA   AH
ZING    BOP     POW
FRRING  CRASH   FOO
BO      DELTA   FO
FIDDLY  ECHO    ZOOOM
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是:

  • 由于传递给快速排序的函数签名compar不具有规定的函数签名,因此编译器不会发出警告int (*compar)(const void *, const void *)。即使在gdb我运行时ptype record_compare_field_1,看起来签名也不包含const *void
  • 输出在某种程度上是正确的?(根据字段一(零索引)排序结果为AAAAA, BOP, CRASH, DELTA, ECHO.

问题是:

  • 为什么/如何运作?这是一种老式的做法吗?
  • 如果我想更改qsort compar正在使用的函数以使用正确的签名,我该怎么做(我一直在努力想出正确的转换)?

谢谢你!

Ian*_*ott 5

int record_compare_field_1();声明没有原型。这是 C17/C18 标准的过时功能。

\n

在函数调用中qsort(database, 5, sizeof(Record), record_compare_field_1);record_compare_field_1参数具有类型int (*)()并且qsort参数也compar具有类型int (*)(const void *, const void *)。C17 6.2.7 中的这条规则允许这样做:

\n
\n

\xe2\x80\x94 如果只有一种类型是带有参数类型列表的函数类型(函数原型),则复合类型是带有参数类型列表的函数原型。

\n
\n

实际的record_compare_field_1函数定义具有原型int record_compare_field_1(Record, Record),其中Record类型由 定义typedef char Record[3][8]。由于数组参数调整为指针,因此与原型相同int record_compare_field_1(char (*)[8], char (*)[8])

\n

qsort将使用错误的原型调用传入的record_compare_field_1函数,从而导致未定义的行为。大多数 C 实现对所有对象指针类型使用相同的表示形式,因此它可以让您摆脱它。

\n

为了正确地做到这一点,该record_compare_field_1函数可以这样定义:

\n
int record_compare_field_1(const void *a, const void *b)\n{\n    const Record *p_rowA = a;\n    const Record *p_rowB = b;\n    return strcmp((*p_rowA)[1], (*p_rowB)[1]);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

  • 请注意,您可以使用 GCC 的“-Wstrict-prototypes”选项在声明没有原型的函数时生成警告。 (2认同)