为什么可以使用签名错误的比较函数调用 qsort 并且编译没有警告
Why can `qsort` be called with a compare function with the wrong signature and compile has no warnings
我正在努力整合一个代码库(将 qsort
compar
函数移动到一个新的头文件/库,这样它就可以在不被 copy/pasta 共享的情况下共享)并注意到一些事情过程中很奇怪。
这是一个示范清单:
#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]);
}
编译并运行:
$ gcc -Wall main.c
$ ./a.out
AH AAAAA AH
ZING BOP POW
FRRING CRASH FOO
BO DELTA FO
FIDDLY ECHO ZOOOM
令我惊讶的是:
- 编译器没有警告,因为传递给快速排序的
compar
函数的签名没有规定的函数签名 int (*compar)(const void *, const void *)
。即使在gdb
,当我运行 ptype record_compare_field_1
,看起来签名也不包含const *void
。
- 输出不知为何正确? (根据字段一(零索引)排序结果
AAAAA, BOP, CRASH, DELTA, ECHO
。
问题是:
- Why/how 这个有用吗?这是一种老派的做法吗?
- 如果我想更改正在使用的
qsort
compar
函数以使用正确的签名,我该怎么做(我一直在努力想出正确的转换)?
谢谢!
int record_compare_field_1();
声明没有原型。这是 C17/C18 标准的过时功能。
在函数调用 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 中的这条规则允许这样做:
— If only one type is a function type with a parameter type list (a function prototype), the composite type is a function prototype with the parameter type list.
实际的 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])
.
qsort
将使用错误的原型调用传入的 record_compare_field_1
函数,从而导致 未定义的行为 。大多数 C 实现对所有对象指针类型使用相同的表示,因此它可以让您摆脱它。
要正确执行此操作,record_compare_field_1
函数可以这样定义:
int record_compare_field_1(const void *a, const void *b)
{
const Record *p_rowA = a;
const Record *p_rowB = b;
return strcmp((*p_rowA)[1], (*p_rowB)[1]);
}
我正在努力整合一个代码库(将 qsort
compar
函数移动到一个新的头文件/库,这样它就可以在不被 copy/pasta 共享的情况下共享)并注意到一些事情过程中很奇怪。
这是一个示范清单:
#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]);
}
编译并运行:
$ gcc -Wall main.c
$ ./a.out
AH AAAAA AH
ZING BOP POW
FRRING CRASH FOO
BO DELTA FO
FIDDLY ECHO ZOOOM
令我惊讶的是:
- 编译器没有警告,因为传递给快速排序的
compar
函数的签名没有规定的函数签名int (*compar)(const void *, const void *)
。即使在gdb
,当我运行ptype record_compare_field_1
,看起来签名也不包含const *void
。 - 输出不知为何正确? (根据字段一(零索引)排序结果
AAAAA, BOP, CRASH, DELTA, ECHO
。
问题是:
- Why/how 这个有用吗?这是一种老派的做法吗?
- 如果我想更改正在使用的
qsort
compar
函数以使用正确的签名,我该怎么做(我一直在努力想出正确的转换)?
谢谢!
int record_compare_field_1();
声明没有原型。这是 C17/C18 标准的过时功能。
在函数调用 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 中的这条规则允许这样做:
— If only one type is a function type with a parameter type list (a function prototype), the composite type is a function prototype with the parameter type list.
实际的 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])
.
qsort
将使用错误的原型调用传入的 record_compare_field_1
函数,从而导致 未定义的行为 。大多数 C 实现对所有对象指针类型使用相同的表示,因此它可以让您摆脱它。
要正确执行此操作,record_compare_field_1
函数可以这样定义:
int record_compare_field_1(const void *a, const void *b)
{
const Record *p_rowA = a;
const Record *p_rowB = b;
return strcmp((*p_rowA)[1], (*p_rowB)[1]);
}