Navigation(slides)

5.12 Complicated Declarations

C is sometimes castigated for the syntax of its declarations, particularly ones that involve pointers to functions. The syntax is an attempt to make the declaration and the use agree; it works well for simple cases, but it can be confusing for the harder ones, because declarations cannot be read left to right, and because parentheses are over-used. The difference between

   1    int *f();       /* f: function returning pointer to int */

and

   1    int (*pf)();    /* pf: pointer to function returning int */ 

illustrates the problem: * is a prefix operator and it has lower precedence than (), so parentheses are necessary to force the proper association.

Although truly complicated declarations rarely arise in practice, it is important to know how to understand them, and, if necessary, how to create them. One good way to synthesize declarations is in small steps with typedef, which is discussed in Section 6.7. As an alternative, in this section we will present a pair of programs that convert from valid C to a word description and back again. The word description reads left to right.

The first, dcl, is the more complex. It converts a C declaration into a word description, as in these examples:

   1 char **argv
   2     argv:  pointer to char
   3 int (*daytab)[13]
   4     daytab:  pointer to array[13] of int
   5 int *daytab[13]
   6     daytab:  array[13] of pointer to int
   7 void *comp()
   8     comp: function returning pointer to void
   9 void (*comp)()
  10     comp: pointer to function returning void
  11 char (*(*x())[])()
  12     x: function returning pointer to array[] of
  13     pointer to function returning char
  14 char (*(*x[3])())[5]
  15     x: array[3] of pointer to function returning
  16     pointer to array[5] of char

dcl is based on the grammar that specifies a declarator, which is spelled out precisely in Appendix A, Section 8.5; this is a simplified form:

dcl:       optional *'s direct-dcl
direct-dcl name
                 (dcl)
                 direct-dcl()
                 direct-dcl[optional size]

In words, a dcl is a direct-dcl, perhaps preceded by *'s. A direct-dcl is a name, or a parenthesized dcl, or a direct-dcl followed by parentheses, or a direct-dcl followed by brackets with an optional size.

This grammar can be used to parse functions. For instance, consider this declarator:

   (*pfa[])()

pfa will be identified as a name and thus as a direct-dcl. Then pfa[] is also a direct-dcl. Then *pfa[] is recognized as a dcl, so (*pfa[]) is a direct-dcl. Then (*pfa[])() is a direct-dcl and thus a dcl. We can also illustrate the parse with a tree like this (where direct-dcl has been abbreviated to dir-dcl):

attachment:pic512.gif

The heart of the dcl program is a pair of functions, dcl and dirdcl, that parse a declaration according to this grammar. Because the grammar is recursively defined, the functions call each other recursively as they recognize pieces of a declaration; the program is called a recursive-descent parser.

   1    /* dcl:  parse a declarator */
   2    void dcl(void)
   3    {
   4        int ns;
   5 
   6        for (ns = 0; gettoken() == '*'; ) /* count *'s */
   7            ns++;
   8        dirdcl();
   9        while (ns-- > 0)
  10            strcat(out, " pointer to");
  11    }
  12 
  13    /* dirdcl:  parse a direct declarator */
  14    void dirdcl(void)
  15    {
  16        int type;
  17 
  18        if (tokentype == '(') {         /* ( dcl ) */
  19            dcl();
  20            if (tokentype != ')')
  21                printf("error: missing )\n");
  22        } else if (tokentype == NAME)  /* variable name */
  23            strcpy(name, token);
  24        else
  25            printf("error: expected name or (dcl)\n");
  26        while ((type=gettoken()) == PARENS || type == BRACKETS)
  27            if (type == PARENS)
  28                strcat(out, " function returning");
  29            else {
  30                strcat(out, " array");
  31                strcat(out, token);
  32                strcat(out, " of");
  33            }
  34    }

Since the programs are intended to be illustrative, not bullet-proof, there are significant restrictions on dcl. It can only handle a simple data type line char or int. It does not handle argument types in functions, or qualifiers like const. Spurious blanks confuse it. It doesn't do much error recovery, so invalid declarations will also confuse it. These improvements are left as exercises.

Here are the global variables and the main routine:

   1    #include <stdio.h>
   2    #include <string.h>
   3    #include <ctype.h>
   4 
   5    #define MAXTOKEN  100
   6 
   7    enum { NAME, PARENS, BRACKETS };
   8 
   9    void dcl(void);
  10    void dirdcl(void);
  11 
  12    int gettoken(void);
  13    int tokentype;           /* type of last token */
  14    char token[MAXTOKEN];    /* last token string */
  15    char name[MAXTOKEN];     /* identifier name */
  16    char datatype[MAXTOKEN]; /* data type = char, int, etc. */
  17    char out[1000];
  18 
  19    main()  /* convert declaration to words */
  20    {
  21        while (gettoken() != EOF) {   /* 1st token on line */
  22            strcpy(datatype, token);  /* is the datatype */
  23            out[0] = '\0';
  24            dcl();       /* parse rest of line */
  25            if (tokentype != '\n')
  26                printf("syntax error\n");
  27            printf("%s: %s %s\n", name, out, datatype);
  28        }
  29        return 0;
  30    }

The function gettoken skips blanks and tabs, then finds the next token in the input; a "token" is a name, a pair of parentheses, a pair of brackets perhaps including a number, or any other single character.

   1    int gettoken(void)  /* return next token */
   2    {
   3        int c, getch(void);
   4        void ungetch(int);
   5        char *p = token;
   6 
   7        while ((c = getch()) == ' ' || c == '\t')
   8            ;
   9        if (c == '(') {
  10            if ((c = getch()) == ')') {
  11                strcpy(token, "()");
  12                return tokentype = PARENS;
  13            } else {
  14                ungetch(c);
  15                return tokentype = '(';
  16            }
  17        } else if (c == '[') {
  18            for (*p++ = c; (*p++ = getch()) != ']'; )
  19                ;
  20            *p = '\0';
  21            return tokentype = BRACKETS;
  22        } else if (isalpha(c)) {
  23            for (*p++ = c; isalnum(c = getch()); )
  24                *p++ = c;
  25            *p = '\0';
  26            ungetch(c);
  27            return tokentype = NAME;
  28        } else
  29            return tokentype = c;
  30 
  31    }

getch and ungetch are discussed in Chapter 4.

Going in the other direction is easier, especially if we do not worry about generating redundant parentheses. The program undcl converts a word description like "x is a function returning a pointer to an array of pointers to functions returning char," which we will express as

    x () * [] * () char

to

   char (*(*x())[])()

The abbreviated input syntax lets us reuse the gettoken function. undcl also uses the same external variables as dcl does.

   1    /* undcl:  convert word descriptions to declarations */
   2    main()
   3    {
   4        int type;
   5        char temp[MAXTOKEN];
   6 
   7        while (gettoken() != EOF) {
   8            strcpy(out, token);
   9            while ((type = gettoken()) != '\n')
  10                if (type == PARENS || type == BRACKETS)
  11                    strcat(out, token);
  12                else if (type == '*') {
  13                    sprintf(temp, "(*%s)", out);
  14                    strcpy(out, temp);
  15                } else if (type == NAME) {
  16                    sprintf(temp, "%s %s", token, out);
  17                    strcpy(out, temp);
  18                } else
  19                    printf("invalid input at %s\n", token);
  20        }
  21        return 0;
  22    }

Exercise 5-18. Make dcl recover from input errors.

Exercise 5-19. Modify undcl so that it does not add redundant parentheses to declarations.

Exercise 5-20. Expand dcl to handle declarations with function argument types, qualifiers like const, and so on.

Navigation(siblings)

ch3n2k.com | Copyright (c) 2004-2020 czk.