Home > Software design >  Why can `qsort` be called with a compare function with the wrong signature and compile has no warnin
Why can `qsort` be called with a compare function with the wrong signature and compile has no warnin

Time:01-11

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 compar function passed to quick sort does not have the prescribed function signature int (*compar)(const void *, const void *). Even in gdb, when I run ptype record_compare_field_1, it looks like the signature does not contain const *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 qsort compar function 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]);
}
  •  Tags:  
  • Related