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

199 Views Asked by At

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;
  • For an official example of this style, see the C17 standard draft (6.7.8 ¶7), which does this for signal from <signal.h>.
  • Given that this style was already legal in C89/C90, it's surprising that K&R don't use it in their book.

Note that I am not asking about the following:

2

There are 2 best solutions below

3
Lover of Structure On BEST ANSWER

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.

0
Jonathan Leffler On

The succinct answer to the headline question ("Is it really legal for K&R to write PFI strcmp, numcmp; where PFI is typedef'd as int (*)(char *, char *)?") is "Yes".

However, what that does depends on where the definition appears, and it probably wasn't what was intended, so the answer is more like a "Yes, but…".

Inside a function, PFI strcmp, numcmp; declares two uninitialized function pointers and hides any external function names spelt the same way. Once initialized to non-null pointers to appropriate functions, they can be used the same as any other function pointer. Note, however, that there isn't a simple way to make them point to the functions strcmp() or numcmp() — those names have been hidden. (You could do it by passing the addresses of the pointer variables to a function that assigns the right pointers, but that's rather circuitous.)

Outside a function, they are still function pointers, and they should conflict with any functions with the same name in the object files linked into the program. The name strcmp could cause complications for a program trying to use the standard library strcmp() function — and an awful lot of programs fall into that category.

You can't declare functions using a typedef for the type of the function. The definition:

PFI strcmp, numcmp;

is not equivalent to:

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

(with or without the optional extern storage class).

Consequently, I think that the claim on p147 of K&R is dubious — either lacking sufficient context to show what they were thinking or actually erroneous.

I'm ignoring the issue that the standard C type for strcmp() does not match the type used by K&R in the book. It is largely tangential to the question.