cfunction-pointerstypedefkernighan-and-ritchie

Is it really legal for K&R to write "PFI strcmp, numcmp;" where PFI is typedef'd as "int (*)(char *, char *)"?


In The C Programming Language (Kernighan and Ritchie, 2nd ed) on p147, the authors show a typedef declaration

typedef int (*PFI)(char *, char *);

(PFI stands for "pointer to function returning an int"), which they claim

can be used in contexts like

PFI strcmp, numcmp;

in the sort program of Chapter 5.

There, strcmp is either K&R's version from p106¹ or the version from <string.h> in the standard library², and numcmp (p121) is a function which converts its arguments to doubles before comparing them numerically:

int strcmp(char *s1, char *s2);
int numcmp(char *s1, char *s2);

¹ It really should be named strcmp_kr to avoid conflicts with the standard library, but this point is not relevant for this question.
² It technically takes const char * arguments, but this difference is not relevant for this question.

The sort program they are referring to is the one in section 5.11 (p118-121). (While it is independently problematic (see also here), the reasons have no bearing on this question.)

I understand all that K&R are doing, but I can't make sense of their statement about "contexts like PFI strcmp, numcmp;". The function signatures of the function designators strcmp and numcmp are as stated just above. Adding a declaration like PFI strcmp; or PFI numcmp; would lead to a compiler error, as strcmp and numcmp are function designators (with function types); they don't have a function pointer type such as PFI, even though they are automatically converted to function pointers in most contexts (C17 standard draft, 6.3.2.1 ¶4) – which is a subtlety.

That is, I can't come up with a decent example where one can actually write something like PFI strcmp, numcmp;. Let's try such a thing, but with a simplified toy function lencmp which compares strings simply by comparing their lengths:

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

typedef int (*PFI)(char *, char *);
int lencmp(char *, char *);

int main(void) {
    PFI cmp = &lencmp;

    printf("%d\n", (*cmp)("de", "abc"));  /* -1 */
    printf("%d\n", (*cmp)("def", "abc"));  /* 0 */
    printf("%d\n", (*cmp)("def", "ab"));  /* 1 */

    return 0;
}

int lencmp(char *s, char *t) {
    size_t len1 = strlen(s), len2 = strlen(t);
    return (len1 > len2) - (len1 < len2);
}

(That one can equivalently write bare PFI cmp = lencmp; in the assignment and bare cmp(...) in the function calls is not what this question is about.)

Well, the above code works and illustrates how one can legally use PFI. But we wouldn't be able to declare lencmp like this

PFI lencmp;

because this would lead to an error due to the initial declaration mismatching the later definition of lencmp.

What do K&R mean by "contexts like PFI strcmp, numcmp;"? Is this legal? Isn't this simply a semantic error in K&R? Surely one could define variables of the function pointer type PFI (that is: int (*)(char *, char *)) and assign strcmp and numcmp to them

PFI fp1 = &strcmp;
PFI fp2 = &numcmp;

but we wouldn't be able to name those variables strcmp and numcmp. (However, as commenter KamilCuk pointed out, we could rename cmp within the function main to one of these names.)

Incidentally, what does work is a typedef to a direct function type:

typedef int FI(char *, char *);
FI lencmp;

Note that I am not asking about the following:


Solution

  • I checked the first edition of The C Programming Language (1978), and the answer is:

    Kernighan and Ritchie updated the following text from the 1st edition (1e: p141)

    For example,

    typedef int (*PFI)();
    

    creates the type PFI, for "pointer to function returning int," which can be used in contexts like

    PFI strcmp, numcmp, swap;
    

    in the sort program of Chapter 5.

    in an incomplete/erroneous way for the 2nd edition, which reads (2e: p147):

    For example,

    typedef int (*PFI)(char *, char *);
    

    creates the type PFI, for "pointer to function (of two char * arguments) returning int," which can be used in contexts like

    PFI strcmp, numcmp;
    

    in the sort program of Chapter 5.

    The text in the first edition refers to the following code (1e: sec5.12 (Pointers to Functions), p115; some comments removed):

    #define LINES 100
    
    main(argc, argv)    /* sort input lines */
    int argc;
    char *argv[];
    {
         char *lineptr[LINES];
         int nlines;
         int strcmp(), numcmp(); /* comparison functions */
         int swap();   /* exchange function */
         numeric = 0;
         
         <body of function>
    }
    

    strcmp, numcmp and swap are addresses of functions; since they are known to be functions, the & operator is not necessary, in the same way that it is not needed before an array name.

    (Don't get confused by the part of the last sentence after the semicolon about the missing ampersand &: this applies to the code in the body of main (not shown here) and equally applies in the 2nd ed; it is not relevant for the issue discussed in this post.)

    The corresponding text in the 2nd edition (2e: sec5.11 (Pointers to Functions), p119) has strcmp and numcmp declared (not within main but) at file scope, and swap appears (not in that code excerpt but) only later when it is called from their qsort (p120) and when it is defined (p121).

    That is, the line PFI strcmp, numcmp, swap; from their 1st ed is intended to replace the main-internal declarations

    int strcmp(), numcmp();
    int swap();
    

    and the corresponding version PFI strcmp, numcmp; from their 2nd ed doesn't make sense; it should have been taken out entirely.

    The implication from the 1st ed text is that declaring the functions strcmp/numcmp/swap with a function pointer type was acceptable in K&R C. Whether this was truly so or there is a mistake on p141 (1e) is something that I can't judge.