C에서는 선언문의 구조가 아주 복잡해지는 경우가 가끔 생긴다 간단한 경우에는 별 무제가 없지만 선언이 복잡해지면 뜻하지 않는 방법으로 선언이 되는 수도 있다. 선언은 외쪽에서 오른쪽으로 차례로 되는것이 아니므로 괄호를 많이 사용하게 되는 경우도 있다 다음의 두문장


int *f(); /* 정수형에 포인터를 리턴하는 함수 */


int (*pf)(); /* 정수를 리턴하는 함수의 포인터 */


를 생각해 보자 *는 앞에 붙는 연산자인데 괄호보다 우선순위가 낮기 때문에두번쨰 예에서는 괄호가 꼭 필요하다. 실제상황에서 아주 복잡하게 얽힌 선언문이 필요한 경우는 거의 없지만 이해할 필요가 있다 선언문을 작성하는 좋은 방법중 하나는 6.7에서 설명할 typedef를 사용하는 것이다 또 한가지 방법으로 여기서는 선언문을 말로 표현해주는 함수와 그 역동작을 하는 함수를 소개한다

우선 선언문을 설명으로 바꾸어 주는 함수 dcl에 대해 설명한다 실행 예를 몇가지 보이면 다음과 같다.


char **argv

argv:  pointer to char

int (*daytab)[13]

daytab: pointer to array[13] of int

int *daytab[13]

daytab: array[13] of pointer to int

void *comp()

comp: function returning pointer to void

void (*comp)()

comp: pointer to function returning void

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

x: function returning pointer to array[] of 

pointer to function returning char

char (*(*x[3])())[5]

x: array[3] of pointer to function returning 

pointer to array[5] of char


dcl은 C의 선언에 관한 문법을 그대로 적용한 프로그램이다 구조는 다음과 같다


dcl: optional *'s direct-dcl


direct-dcl name

(dcl)

direct-dcl()

direct-dcl[optional size]


위에서 보듯이 direct-dcl을 선언 할떄 사용되는 이름등 직접적으로 선언하는데 사용되는 단어를 말하고 dcl은 그 단어앞에 *가 붙어서 포인터임을 나타내는것을 말한다 direct-dcl 뒤에()나[]가 붙은것도 direct-dcl이 된다 위의 설명을 잘 모르겠으면 다음의 예를 보자


(*pfa[])()


pfa는 이름이므로 direct-dcl이다 pfa[]도 direct-dcl이고 *pfa[]는 dcl로 인식된다. 그래서 (*pfa[])는 direct-dcl,(*pfa[])()는direct-dcl 이다


dcl 프로그램의 가장 중요한 부분은 dcl과 dirdcl이라 불리는 두 함수이다 이 두 프로그램은 되부름에 의해 반복적으로 불리면서 선언문을 분석해 낸다.


/* dcl: parse a declarator */

void dcl(void)

{

    int ns;

    for (ns = 0; gettoken() == '*'; ) /* count *'s */

        ns++;

    dirdcl();

    while (ns-- > 0)

        strcat(out, " pointer to");

}


/* dirdcl: parse a direct declarator */

void dirdcl(void)

{

    int type;

    

    if (tokentype == '(') {   /* ( dcl ) */

        dcl();

        

        if (tokentype != ')')

            printf("error: missing )\n");

    } else if (tokentype == NAME) /* variable name */

        strcpy(name, token);

    else

        printf("error: expected name or (dcl)\n");

    while ((type=gettoken()) == PARENS || type == BRACKETS)

        if (type == PARENS)

            strcat(out, " function returning");

        else {

            strcat(out, " array");

            strcat(out, token);

            strcat(out, " of");

         }

}


이 두 프로그램은 설명을 위해 만들어진 것 이기때문에 실제 사용하기에는 무리가 있다 데이터의 형도 문자형과 정수형밖에 다루지 못한다. 그러므로 함수 const 등을 처리하지 못한다 공백을 잘못쓰면 프로그램이 오작동하는 경우도 있다. 개선은 연습으로 남기고 우선 주 프로그램을 보인다


#include <stdio.h> 

#include <string.h> 

#include <ctype.h>


#define MAXTOKEN 100


enum { NAME, PARENS, BRACKETS };


void dcl(void);

void dirdcl(void);


int gettoken(void);

int tokentype;          /* type of last token */

char token[MAXTOKEN];   /* last token string */

char name[MAXTOKEN];    /* identifier name */

char datatype[MAXTOKEN];/* data type = char, int, etc. */

char out[1000];         /* output string */


int main() /* convert declaration to words */

{

    while (gettoken() != EOF) { /* 1st token on line */

        strcpy(datatype, token); /* is the datatype */

        out[0] = '\0';

        dcl(); /* parse rest of line */

        if (tokentype != '\n')

            printf("syntax error\n");

        printf("%s: %s %s\n", name, out, datatype);

    }

    return 0;

}


gettoken 함수는 빈칸이나 탭은 무시하고 입력중에서 다음번 토큰을 찾는 함수이다. 토큰이라 함은 이름,숫자나 문자 한자가 들어있는 괄호나 대괄호등을 말한다.


int gettoken(void) /* return next token */ {

    

    int c, getch(void);

    void ungetch(int);

    char *p = token;

    

    while ((c = getch()) == ' ' || c == '\t')

        ;

    if (c == '(') {

        if ((c = getch()) == ')') {

            strcpy(token, "()");

            return tokentype = PARENS;

        } else {

            ungetch(c);

            return tokentype = '(';

        }

    } else if (c == '[') {

        for (*p++ = c; (*p++ = getch()) != ']'; )

            ;

        *p = '\0';

        return tokentype = BRACKETS;

    } else if (isalpha(c)) {

        for (*p++ = c; isalnum(c = getch()); )

            *p++ = c;

        *p = '\0';

        ungetch(c);

        return tokentype = NAME;

    } else

            return tokentype = c;

}


getch와 ungetch는 제 4장에서 설명했던 것이다.

말로 설명된 것을 선언문으로 바꾸는 것은 어렵지 않다 만약 x는 함수이고 리턴값은 문자를 리턴하는 함수들의 포인터 배열의 포인터라는 말을 


x()*[]*() char


라고 표현하면


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


라고 표현해주는 함수 undcl을 생각한다. 말을 그대로 입력하지 않고 요약된 표현으로 된 입력을 사용하기 때문에 gettoken 함수를 사용할수 있다. undcl은 dcl과 같은 외부변수를 사용한다 프로그램은 다음과 같다.


예제 5-18 dcl의 입력에 에러가 있으면 고칠 수 있도록 해보라.


예제 5-19 undcl을 수정해서 결과로 나온 선언문에 필요 없는 괄호가 들어가지 않도록 하라.


예제 5-20 dcl을 보강해서 함수 매개변수나 그외 const등도 처리할 수있도록 만들어라.