I was working on consolidating a code base (moving a qsort compar function to a new header /library so that it could be shared without being copy/pasta) and noticed something strange in the process.
Here is a demonstrative listing:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
/** One record has five 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]);
}
Compile and run:
$ gcc -Wall main.c
$ ./a.out
AH AAAAA AH
ZING BOP POW
FRRING CRASH FOO
BO DELTA FO
FIDDLY ECHO ZOOOM
It's surprising to me that:
- The compiler has no warnings since the signature of the
comparfunction passed to quick sort does not have the prescribed function signatureint (*compar)(const void *, const void *). Even ingdb, when I runptype record_compare_field_1, it looks like the signature does not containconst *void. - The output is somehow correct? (Sorted based on field one (zero-indexed) results in
AAAAA, BOP, CRASH, DELTA, ECHO.
The questions are:
- Why/how does this work? Is this an old-school way of doing this?
- If I wanted to change the
qsortcomparfunction in use to use the proper signature, how would I do that (I been struggling trying to come up with the proper casts)?
Thank you!
CodePudding user response:
The int record_compare_field_1(); declaration does not have a prototype. This is an obsolescent feature of the C17/C18 standard.
In the function call qsort(database, 5, sizeof(Record), record_compare_field_1);, the record_compare_field_1 argument has type int (*)() and qsort's compar parameter has type int (*)(const void *, const void *). This is allowed by this rule from 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.
The actual record_compare_field_1 function definition has the prototype int record_compare_field_1(Record, Record) where the Record type is defined by typedef char Record[3][8]. Since array parameters are adjusted to pointers, this is the same as the prototype int record_compare_field_1(char (*)[8], char (*)[8]).
qsort will call the passed in record_compare_field_1 function with the wrong prototype, leading to undefined behavior. Most C implementations use the same representation for all object pointer types, so it lets you get away with it.
To do it properly, the record_compare_field_1 function could be defined like this:
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]);
}
